88
99import SwiftUI
1010
11- public struct BBTableView < Data, Content> : UIViewControllerRepresentable where Data : RandomAccessCollection , Content : View , Data. Element : Equatable {
11+ public extension BBTableView {
12+ func bb_reloadData( _ reloadData: Binding < Bool > ) -> Self {
13+ var view = self
14+ view. _reloadData = reloadData
15+ return view
16+ }
17+
18+ func bb_reloadRows( _ reloadRows: Binding < [ Int ] > ) -> Self {
19+ var view = self
20+ view. _reloadRows = reloadRows
21+ return view
22+ }
23+
24+ func bb_contentOffset( _ contentOffset: Binding < CGPoint > ) -> Self {
25+ var view = self
26+ view. _contentOffset = contentOffset
27+ return view
28+ }
29+
30+ func bb_contentOffsetToScrollAnimated( _ contentOffsetToScrollAnimated: Binding < CGPoint ? > ) -> Self {
31+ var view = self
32+ view. _contentOffsetToScrollAnimated = contentOffsetToScrollAnimated
33+ return view
34+ }
35+ }
36+
37+ public struct BBTableView < Data, Content> : UIViewControllerRepresentable , BBUIScrollViewRepresentable where Data : RandomAccessCollection , Content : View , Data. Element : Equatable {
1238 let data : Data
1339 let content : ( Data . Element ) -> Content
40+
41+ @Binding public var reloadData : Bool
42+ @Binding public var reloadRows : [ Int ]
43+ @Binding public var contentOffset : CGPoint
44+ @Binding public var contentOffsetToScrollAnimated : CGPoint ?
45+ public var isPagingEnabled : Bool
46+ public var bounces : Bool
47+ public var alwaysBounceVertical : Bool
48+ public var alwaysBounceHorizontal : Bool
49+ public var showsVerticalScrollIndicator : Bool
50+ public var showsHorizontalScrollIndicator : Bool
1451
15- public init ( _ data: Data , @ViewBuilder content: @escaping ( Data . Element ) -> Content ) {
52+ public init ( _ data: Data ,
53+ reloadData: Binding < Bool > = . constant( false ) ,
54+ reloadRows: Binding < [ Int ] > = . constant( [ ] ) ,
55+ contentOffset: Binding < CGPoint > = . constant( . bb_invalidContentOffset) ,
56+ contentOffsetToScrollAnimated: Binding < CGPoint ? > = . constant( nil ) ,
57+ isPagingEnabled: Bool = false ,
58+ bounces: Bool = true ,
59+ alwaysBounceVertical: Bool = false ,
60+ alwaysBounceHorizontal: Bool = false ,
61+ showsVerticalScrollIndicator: Bool = true ,
62+ showsHorizontalScrollIndicator: Bool = true ,
63+ @ViewBuilder content: @escaping ( Data . Element ) -> Content )
64+ {
1665 self . data = data
1766 self . content = content
67+ self . _reloadData = reloadData
68+ self . _reloadRows = reloadRows
69+ self . _contentOffset = contentOffset
70+ self . _contentOffsetToScrollAnimated = contentOffsetToScrollAnimated
71+ self . isPagingEnabled = isPagingEnabled
72+ self . bounces = bounces
73+ self . alwaysBounceVertical = alwaysBounceVertical
74+ self . alwaysBounceHorizontal = alwaysBounceHorizontal
75+ self . showsVerticalScrollIndicator = showsVerticalScrollIndicator
76+ self . showsHorizontalScrollIndicator = showsHorizontalScrollIndicator
1877 }
1978
2079 public func makeUIViewController( context: Context ) -> UIViewController {
2180 _BBTableViewController ( self )
2281 }
2382
24- public func updateUIViewController( _ vc: UIViewController , context: Context ) {
25- ( vc as! _BBTableViewController ) . update ( self )
83+ public func updateUIViewController( _ viewController: UIViewController , context: Context ) {
84+ let vc = viewController as! _BBTableViewController < Data , Content >
85+ updateScrollView ( vc. tableView)
86+ vc. update ( self )
2687 }
2788}
2889
29- private class _BBTableViewController < Data, Content> : UIViewController , UITableViewDataSource where Data: RandomAccessCollection , Content: View , Data. Element: Equatable {
90+ private class _BBTableViewController < Data, Content> : UIViewController , UITableViewDataSource , UITableViewDelegate where Data: RandomAccessCollection , Content: View , Data. Element: Equatable {
3091 var representable : BBTableView < Data , Content >
3192 var tableView : UITableView !
3293
@@ -50,6 +111,7 @@ private class _BBTableViewController<Data, Content>: UIViewController, UITableVi
50111 tableView. register ( _BBTableViewHostCell< Content> . self , forCellReuseIdentifier: " cell " )
51112 tableView. separatorStyle = . none
52113 tableView. dataSource = self
114+ tableView. delegate = self
53115 view. addSubview ( tableView)
54116
55117 NSLayoutConstraint . activate ( [
@@ -61,26 +123,52 @@ private class _BBTableViewController<Data, Content>: UIViewController, UITableVi
61123 }
62124
63125 func update( _ newRepresentable: BBTableView < Data , Content > ) {
64- if tableView. window == nil { return }
65-
66- var removals : [ IndexPath ] = [ ]
67- var insertions : [ IndexPath ] = [ ]
68- let diff = newRepresentable. data. difference ( from: data)
69- for step in diff {
70- switch step {
71- case let . remove( i, _, _) : removals. append ( IndexPath ( row: i, section: 0 ) )
72- case let . insert( i, _, _) : insertions. append ( IndexPath ( row: i, section: 0 ) )
126+ if newRepresentable. reloadData {
127+ representable = newRepresentable
128+ tableView. reloadData ( )
129+
130+ DispatchQueue . main. async {
131+ self . representable. reloadData = false
132+ self . representable. reloadRows. removeAll ( )
133+ }
134+ } else {
135+ var removals : [ IndexPath ] = [ ]
136+ var insertions : [ IndexPath ] = [ ]
137+ let diff = newRepresentable. data. difference ( from: data)
138+ for step in diff {
139+ switch step {
140+ case let . remove( i, _, _) : removals. append ( IndexPath ( row: i, section: 0 ) )
141+ case let . insert( i, _, _) : insertions. append ( IndexPath ( row: i, section: 0 ) )
142+ }
143+ }
144+
145+ representable = newRepresentable
146+
147+ if !removals. isEmpty || !insertions. isEmpty {
148+ tableView. performBatchUpdates ( {
149+ tableView. deleteRows ( at: removals, with: . automatic)
150+ tableView. insertRows ( at: insertions, with: . automatic)
151+ } , completion: nil )
152+ }
153+
154+ if !representable. reloadRows. isEmpty {
155+ tableView. reloadRows ( at: representable. reloadRows. map { IndexPath ( row: $0, section: 0 ) } , with: . automatic)
156+
157+ DispatchQueue . main. async {
158+ self . representable. reloadRows. removeAll ( )
159+ }
73160 }
74161 }
75162
76- representable = newRepresentable
77-
78- tableView. beginUpdates ( )
79- if !removals. isEmpty { tableView. deleteRows ( at: removals, with: . automatic) }
80- if !insertions. isEmpty { tableView. insertRows ( at: insertions, with: . automatic) }
81- tableView. endUpdates ( )
82-
83- if let visibleIndexPaths = tableView. indexPathsForVisibleRows { tableView. reloadRows ( at: visibleIndexPaths, with: . automatic) }
163+ // TODO: Scroll to row
164+ if let contentOffset = representable. contentOffsetToScrollAnimated {
165+ tableView. setContentOffset ( contentOffset, animated: true )
166+ DispatchQueue . main. async {
167+ self . representable. contentOffsetToScrollAnimated = nil
168+ }
169+ } else if representable. contentOffset != . bb_invalidContentOffset {
170+ tableView. contentOffset = representable. contentOffset
171+ }
84172 }
85173
86174 // MARK: UITableViewDataSource
@@ -94,6 +182,14 @@ private class _BBTableViewController<Data, Content>: UIViewController, UITableVi
94182 cell. update ( view, parent: self )
95183 return cell
96184 }
185+
186+ // MARK: UITableViewDelegate
187+
188+ func scrollViewDidScroll( _ scrollView: UIScrollView ) {
189+ DispatchQueue . main. async {
190+ self . representable. contentOffset = scrollView. contentOffset
191+ }
192+ }
97193}
98194
99195private class _BBTableViewHostCell < Content: View > : UITableViewCell {
0 commit comments