@@ -7,121 +7,121 @@ import UIKit
7
7
import Combine
8
8
9
9
public class TableViewBatchesController < Element: Hashable > {
10
- // Input
11
- public let reload = PassthroughSubject < Void , Never > ( )
12
- public let loadNext = PassthroughSubject < Void , Never > ( )
13
-
14
- // Output
15
- public let loadError = CurrentValueSubject < Error ? , Never > ( nil )
16
-
17
- // Private user interface
18
- private let tableView : UITableView
19
- private var batchesDataSource : BatchesDataSource < Element > !
20
- private var spin : UIActivityIndicatorView = {
21
- let spin = UIActivityIndicatorView ( style: . large)
22
- spin. tintColor = . systemGray
23
- spin. startAnimating ( )
24
- spin. alpha = 0
25
- return spin
26
- } ( )
27
-
28
- private var itemsController : TableViewItemsController < [ [ Element ] ] > !
29
- private var subscriptions = [ AnyCancellable] ( )
30
-
31
- public convenience init ( tableView: UITableView , itemsController: TableViewItemsController < [ [ Element ] ] > , initialToken: Data ? , loadItemsWithToken: @escaping ( Data ? ) -> AnyPublisher < BatchesDataSource < Element > . LoadResult , Error > ) {
32
- self . init ( tableView: tableView)
33
-
34
- // Create a token-based batched data source.
35
- batchesDataSource = BatchesDataSource < Element > (
36
- input: BatchesInput ( reload: reload. eraseToAnyPublisher ( ) , loadNext: loadNext. eraseToAnyPublisher ( ) ) ,
37
- initialToken: initialToken,
38
- loadItemsWithToken: loadItemsWithToken
39
- )
40
-
41
- self . itemsController = itemsController
42
-
43
- bind ( )
44
- }
45
-
46
- public convenience init ( tableView: UITableView , itemsController: TableViewItemsController < [ [ Element ] ] > , loadPage: @escaping ( Int ) -> AnyPublisher < BatchesDataSource < Element > . LoadResult , Error > ) {
47
- self . init ( tableView: tableView)
48
-
49
- // Create a paged data source.
50
- self . batchesDataSource = BatchesDataSource < Element > (
51
- input: BatchesInput ( reload: reload. eraseToAnyPublisher ( ) , loadNext: loadNext. eraseToAnyPublisher ( ) ) ,
52
- loadPage: loadPage
53
- )
10
+ // Input
11
+ public let reload = PassthroughSubject < Void , Never > ( )
12
+ public let loadNext = PassthroughSubject < Void , Never > ( )
54
13
55
- self . itemsController = itemsController
14
+ // Output
15
+ public let loadError = CurrentValueSubject < Error ? , Never > ( nil )
56
16
57
- bind ( )
58
- }
59
-
60
- private init ( tableView: UITableView ) {
61
- self . tableView = tableView
62
-
63
- // Add bottom offset.
64
- var newInsets = tableView. contentInset
65
- newInsets. bottom += 60
66
- tableView. contentInset = newInsets
67
-
68
- // Add spinner.
69
- tableView. addSubview ( spin)
70
- }
71
-
72
- private func bind( ) {
73
- // Display items in table view.
74
- batchesDataSource. output. $items
75
- . receive ( on: DispatchQueue . main)
76
- . bind ( subscriber: tableView. rowsSubscriber ( itemsController) )
77
- . store ( in: & subscriptions)
17
+ // Private user interface
18
+ private let tableView : UITableView
19
+ private var batchesDataSource : BatchesDataSource < Element > !
20
+ private var spin : UIActivityIndicatorView = {
21
+ let spin = UIActivityIndicatorView ( style: . large)
22
+ spin. tintColor = . systemGray
23
+ spin. startAnimating ( )
24
+ spin. alpha = 0
25
+ return spin
26
+ } ( )
78
27
79
- // Show/hide spinner.
80
- batchesDataSource. output. $isLoading
81
- . receive ( on: DispatchQueue . main)
82
- . sink { [ weak self] isLoading in
83
- guard let self = self else { return }
84
- if isLoading {
85
- self . spin. center = CGPoint ( x: self . tableView. frame. width/ 2 , y: self . tableView. contentSize. height + 30 )
86
- self . spin. alpha = 1
87
- self . tableView. scrollRectToVisible ( CGRect ( x: 0 , y: self . tableView. contentOffset. y + self . tableView. frame. height, width: 10 , height: 10 ) , animated: true )
88
- } else {
89
- self . spin. alpha = 0
90
- }
91
- }
92
- . store ( in: & subscriptions)
28
+ private var itemsController : TableViewItemsController < [ [ Element ] ] > !
29
+ private var subscriptions = [ AnyCancellable] ( )
93
30
94
- // Bind errors.
95
- batchesDataSource. output. $error
96
- . subscribe ( loadError)
97
- . store ( in: & subscriptions)
31
+ public convenience init ( tableView: UITableView , itemsController: TableViewItemsController < [ [ Element ] ] > , initialToken: Data ? , loadItemsWithToken: @escaping ( Data ? ) -> AnyPublisher < BatchesDataSource < Element > . LoadResult , Error > ) {
32
+ self . init ( tableView: tableView)
33
+
34
+ // Create a token-based batched data source.
35
+ batchesDataSource = BatchesDataSource < Element > (
36
+ input: BatchesInput ( reload: reload. eraseToAnyPublisher ( ) , loadNext: loadNext. eraseToAnyPublisher ( ) ) ,
37
+ initialToken: initialToken,
38
+ loadItemsWithToken: loadItemsWithToken
39
+ )
40
+
41
+ self . itemsController = itemsController
42
+
43
+ bind ( )
44
+ }
98
45
99
- // Observe for table dragging.
100
- let didDrag = Publishers . CombineLatest ( Just ( tableView) , tableView. publisher ( for: \. contentOffset) )
101
- . map { $0. 0 . isDragging }
102
- . scan ( ( from: false , to: false ) ) { result, value -> ( from: Bool , to: Bool ) in
103
- return ( from: result. to, to: value)
104
- }
105
- . filter { tuple -> Bool in
106
- tuple == ( from: true , to: false )
107
- }
108
-
109
- // Observe table offset and trigger loading next page at bottom
110
- Publishers . CombineLatest ( Just ( tableView) , didDrag)
111
- . map { $0. 0 }
112
- . filter { table -> Bool in
113
- return isAtBottom ( of: table)
114
- }
115
- . sink { [ weak self] _ in
116
- self ? . loadNext. send ( )
117
- }
118
- . store ( in: & subscriptions)
119
- }
46
+ public convenience init ( tableView: UITableView , itemsController: TableViewItemsController < [ [ Element ] ] > , loadPage: @escaping ( Int ) -> AnyPublisher < BatchesDataSource < Element > . LoadResult , Error > ) {
47
+ self . init ( tableView: tableView)
48
+
49
+ // Create a paged data source.
50
+ self . batchesDataSource = BatchesDataSource < Element > (
51
+ input: BatchesInput ( reload: reload. eraseToAnyPublisher ( ) , loadNext: loadNext. eraseToAnyPublisher ( ) ) ,
52
+ loadPage: loadPage
53
+ )
54
+
55
+ self . itemsController = itemsController
56
+
57
+ bind ( )
58
+ }
59
+
60
+ private init ( tableView: UITableView ) {
61
+ self . tableView = tableView
62
+
63
+ // Add bottom offset.
64
+ var newInsets = tableView. contentInset
65
+ newInsets. bottom += 60
66
+ tableView. contentInset = newInsets
67
+
68
+ // Add spinner.
69
+ tableView. addSubview ( spin)
70
+ }
71
+
72
+ private func bind( ) {
73
+ // Display items in table view.
74
+ batchesDataSource. output. $items
75
+ . receive ( on: DispatchQueue . main)
76
+ . bind ( subscriber: tableView. rowsSubscriber ( itemsController) )
77
+ . store ( in: & subscriptions)
78
+
79
+ // Show/hide spinner.
80
+ batchesDataSource. output. $isLoading
81
+ . receive ( on: DispatchQueue . main)
82
+ . sink { [ weak self] isLoading in
83
+ guard let self = self else { return }
84
+ if isLoading {
85
+ self . spin. center = CGPoint ( x: self . tableView. frame. width/ 2 , y: self . tableView. contentSize. height + 30 )
86
+ self . spin. alpha = 1
87
+ self . tableView. scrollRectToVisible ( CGRect ( x: 0 , y: self . tableView. contentOffset. y + self . tableView. frame. height, width: 10 , height: 10 ) , animated: true )
88
+ } else {
89
+ self . spin. alpha = 0
90
+ }
91
+ }
92
+ . store ( in: & subscriptions)
93
+
94
+ // Bind errors.
95
+ batchesDataSource. output. $error
96
+ . subscribe ( loadError)
97
+ . store ( in: & subscriptions)
98
+
99
+ // Observe for table dragging.
100
+ let didDrag = Publishers . CombineLatest ( Just ( tableView) , tableView. publisher ( for: \. contentOffset) )
101
+ . map { $0. 0 . isDragging }
102
+ . scan ( ( from: false , to: false ) ) { result, value -> ( from: Bool , to: Bool ) in
103
+ return ( from: result. to, to: value)
104
+ }
105
+ . filter { tuple -> Bool in
106
+ tuple == ( from: true , to: false )
107
+ }
108
+
109
+ // Observe table offset and trigger loading next page at bottom
110
+ Publishers . CombineLatest ( Just ( tableView) , didDrag)
111
+ . map { $0. 0 }
112
+ . filter { table -> Bool in
113
+ return isAtBottom ( of: table)
114
+ }
115
+ . sink { [ weak self] _ in
116
+ self ? . loadNext. send ( )
117
+ }
118
+ . store ( in: & subscriptions)
119
+ }
120
120
}
121
121
122
122
fileprivate func isAtBottom( of tableView: UITableView ) -> Bool {
123
- let height = tableView. frame. size. height
124
- let contentYoffset = tableView. contentOffset. y
125
- let distanceFromBottom = tableView. contentSize. height - contentYoffset
126
- return distanceFromBottom <= height
123
+ let height = tableView. frame. size. height
124
+ let contentYoffset = tableView. contentOffset. y
125
+ let distanceFromBottom = tableView. contentSize. height - contentYoffset
126
+ return distanceFromBottom <= height
127
127
}
0 commit comments