@@ -38,12 +38,24 @@ public protocol TableCellLifeCycleObserver: AnyObject {
38
38
func tableView( _ tableView: TableView , didRemoveCellFromViewport cell: TableCell )
39
39
}
40
40
41
+
42
+ public enum ViewportBorderDisplay {
43
+ case hidden
44
+ case visible( color: UIColor , borderWidth: CGFloat )
45
+ }
46
+
41
47
/// An object capable of handing `TableView` events
42
48
public protocol TableViewDelegate : AnyObject {
43
49
var containerScrollView : UIScrollView ? { get }
44
50
45
51
var viewport : CGRect ? { get }
46
52
53
+ /// Governs whether resolved viewport is displayed
54
+ /// - Note: This may be used for debugging purposes.
55
+ /// - Important: It is responsibility of consumer of the API to ensure that this is not displayed in app if not intended to. i.e. display of viewport does not
56
+ /// check for DEBUG flags and would be displayed based on value provided.
57
+ var resolvedViewportBorderDisplay : ViewportBorderDisplay { get }
58
+
47
59
/// Invoked when `EditorView` within the cell receives focus
48
60
/// - Parameters:
49
61
/// - tableView: TableView containing cell
@@ -145,6 +157,10 @@ public protocol TableViewDelegate: AnyObject {
145
157
func tableView( _ tableView: TableView , didRemoveCellFromViewport cell: TableCell )
146
158
}
147
159
160
+ public extension TableViewDelegate {
161
+ var resolvedViewportBorderDisplay : ViewportBorderDisplay { . hidden }
162
+ }
163
+
148
164
/// A view that provides a tabular structure where each cell is an `EditorView`.
149
165
/// Since the cells contains an `EditorView` in itself, it is capable of hosting any attachment that `EditorView` can host
150
166
/// including another `TableView` as an attachment.
@@ -165,6 +181,12 @@ public class TableView: UIView {
165
181
166
182
private let repository = TableCellRepository ( )
167
183
184
+ private var _containerScrollView : UIScrollView ? {
185
+ didSet {
186
+ _containerScrollView != nil ? setupScrollObserver ( ) : removeScrollObserver ( )
187
+ }
188
+ }
189
+
168
190
private lazy var columnRightBorderView : UIView = {
169
191
makeSelectionBorderView ( )
170
192
} ( )
@@ -345,6 +367,40 @@ public class TableView: UIView {
345
367
}
346
368
}
347
369
370
+ public override func didMoveToWindow( ) {
371
+ guard window != nil else {
372
+ removeScrollObserver ( )
373
+ return
374
+ }
375
+
376
+ // Only try to auto resolve container scrollview, if not already provided by the delegate
377
+ guard self . containerScrollView == nil else { return }
378
+
379
+ // If table has the Editor which is scrollable, use that as container for viewport
380
+ let containerEditorView = self . containerAttachment? . containerEditorView
381
+ if let scrollView = containerEditorView? . scrollView, scrollView. isScrollEnabled {
382
+ _containerScrollView = scrollView
383
+ return
384
+ }
385
+
386
+ // Else, find the next available scrollview up the hierarchy
387
+ var currentView : UIView ? = containerEditorView
388
+ while let view = currentView, !( view is UIScrollView ) {
389
+ currentView = view. superview
390
+ }
391
+
392
+ if let scrollView = currentView as? UIScrollView {
393
+ _containerScrollView = scrollView
394
+ }
395
+
396
+ // If there's still none, default to container editor scrollview
397
+ // This would typically be the case where the Editor starts off as non-scrollable but becomes scrollable
398
+ // as the content overflows in which case this should resolve correctly.
399
+ if _containerScrollView == nil {
400
+ _containerScrollView = containerEditorView? . scrollView
401
+ }
402
+ }
403
+
348
404
/// Maintains the scroll lock on the cell passed in if the original rect ends up moving as a result of cells getting rendered above this rect position
349
405
/// - Parameters:
350
406
/// - cell: Cell to lock on
@@ -399,7 +455,7 @@ public class TableView: UIView {
399
455
}
400
456
401
457
private func setupScrollObserver( ) {
402
- observation = delegate ? . containerScrollView? . observe ( \. bounds, options: [ . new, . old] ) { [ weak self] container, change in
458
+ observation = containerScrollView? . observe ( \. bounds, options: [ . new, . old] ) { [ weak self] container, change in
403
459
self ? . viewportChanged ( )
404
460
}
405
461
}
@@ -450,7 +506,7 @@ public class TableView: UIView {
450
506
// ensure editor is not hidden e.g. inside an Expand in collapsed state
451
507
attachmentContentView. attachment? . containerEditorView? . isHidden == false ,
452
508
tableView. bounds != . zero,
453
- let containerScrollView = delegate ? . containerScrollView,
509
+ let containerScrollView = self . containerScrollView,
454
510
let rootEditorView = containerAttachment? . containerEditorView? . rootEditor else {
455
511
cellsInViewport = [ ]
456
512
return
@@ -472,8 +528,10 @@ public class TableView: UIView {
472
528
// Convert the visible rectangle back to the nestedView's coordinate space
473
529
let visibleRectOfNestedView = rootEditorView. convert ( visibleRectOfNestedViewInScrollView, from: containerScrollView)
474
530
475
- // Uncomment following line to show the resolved viewport
476
- // Utility.drawRect(rect: visibleRectOfNestedView, color: .red, in: rootEditorView, name: "viewport")
531
+ if let viewportBorder = delegate? . resolvedViewportBorderDisplay,
532
+ case let ViewportBorderDisplay . visible( color, borderWidth) = viewportBorder {
533
+ Utility . drawRect ( rect: visibleRectOfNestedView, color: color, borderWidth: borderWidth, in: rootEditorView, name: " viewport " )
534
+ }
477
535
478
536
let adjustedViewport = visibleRectOfNestedView. offsetBy ( dx: tableView. bounds. minX, dy: tableView. bounds. minY)
479
537
@@ -834,7 +892,7 @@ extension TableView: UIScrollViewDelegate {
834
892
835
893
extension TableView : TableContentViewDelegate {
836
894
var containerScrollView : UIScrollView ? {
837
- delegate? . containerScrollView
895
+ delegate? . containerScrollView ?? _containerScrollView
838
896
}
839
897
840
898
var viewport : CGRect ? {
0 commit comments