@@ -7,121 +7,121 @@ import UIKit
77import Combine
88
99public 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 > ( )
5413
55- self . itemsController = itemsController
14+ // Output
15+ public let loadError = CurrentValueSubject < Error ? , Never > ( nil )
5616
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+ } ( )
7827
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] ( )
9330
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+ }
9845
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+ }
120120}
121121
122122fileprivate 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
127127}
0 commit comments