From 57a78cc948081ca8d049fcad5d8802da14336166 Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Thu, 14 May 2026 15:10:40 -0500 Subject: [PATCH 1/7] feat: add keyboard adjustment insets --- ListableUI/Sources/Behavior.swift | 8 ++++ ListableUI/Sources/ListView/ListView.swift | 11 +++++- ListableUI/Tests/BehaviorTests.swift | 1 + ListableUI/Tests/ListView/ListViewTests.swift | 37 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/ListableUI/Sources/Behavior.swift b/ListableUI/Sources/Behavior.swift index 02415906..5c5b0546 100644 --- a/ListableUI/Sources/Behavior.swift +++ b/ListableUI/Sources/Behavior.swift @@ -21,6 +21,12 @@ public struct Behavior : Equatable /// How to adjust the `contentInset` of the list when the keyboard visibility changes. public var keyboardAdjustmentMode : KeyboardAdjustmentMode + + /// Additional insets to apply while adjusting the list for an overlapping keyboard. + /// + /// This is useful for persistent overlays, such as floating action bars, that should be treated + /// as unavailable space when UIKit scrolls the first responder into view. + public var keyboardAdjustmentAdditionalInsets : UIEdgeInsets /// How the list should react when the user taps the application status bar. /// The default value of this enables scrolling to top. @@ -64,6 +70,7 @@ public struct Behavior : Equatable isScrollEnabled: Bool = true, keyboardDismissMode : UIScrollView.KeyboardDismissMode = .interactive, keyboardAdjustmentMode : KeyboardAdjustmentMode = .adjustsWhenVisible, + keyboardAdjustmentAdditionalInsets : UIEdgeInsets = .zero, scrollsToTop : ScrollsToTop = .enabled, selectionMode : SelectionMode = .single, underflow : Underflow = Underflow(), @@ -77,6 +84,7 @@ public struct Behavior : Equatable self.isScrollEnabled = isScrollEnabled self.keyboardDismissMode = keyboardDismissMode self.keyboardAdjustmentMode = keyboardAdjustmentMode + self.keyboardAdjustmentAdditionalInsets = keyboardAdjustmentAdditionalInsets self.scrollsToTop = scrollsToTop diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index 651b6b30..57ddb814 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -467,12 +467,19 @@ public final class ListView : UIView } }() + let keyboardAdjustmentAdditionalInsets: UIEdgeInsets = keyboardBottomInset > 0.0 + ? self.behavior.keyboardAdjustmentAdditionalInsets + : .zero + let scrollInsets = modified(self.scrollIndicatorInsets) { - $0.bottom = max($0.bottom, keyboardBottomInset) + $0.bottom = max($0.bottom, keyboardBottomInset + keyboardAdjustmentAdditionalInsets.bottom) } let contentInsets = modified(self.collectionView.contentInset) { - $0.bottom = keyboardBottomInset + $0.top = keyboardAdjustmentAdditionalInsets.top + $0.left = keyboardAdjustmentAdditionalInsets.left + $0.bottom = keyboardBottomInset + keyboardAdjustmentAdditionalInsets.bottom + $0.right = keyboardAdjustmentAdditionalInsets.right } return .init( diff --git a/ListableUI/Tests/BehaviorTests.swift b/ListableUI/Tests/BehaviorTests.swift index 2189562c..99703dc7 100644 --- a/ListableUI/Tests/BehaviorTests.swift +++ b/ListableUI/Tests/BehaviorTests.swift @@ -18,6 +18,7 @@ class BehaviorTests: XCTestCase XCTAssertEqual(behavior.keyboardDismissMode, .interactive) XCTAssertEqual(behavior.keyboardAdjustmentMode, .adjustsWhenVisible) + XCTAssertEqual(behavior.keyboardAdjustmentAdditionalInsets, .zero) XCTAssertEqual(behavior.selectionMode, .single) diff --git a/ListableUI/Tests/ListView/ListViewTests.swift b/ListableUI/Tests/ListView/ListViewTests.swift index 4846e55d..14d08474 100644 --- a/ListableUI/Tests/ListView/ListViewTests.swift +++ b/ListableUI/Tests/ListView/ListViewTests.swift @@ -157,6 +157,43 @@ class ListViewTests: XCTestCase UIEdgeInsets(top: 10, left: 0, bottom: 200, right: 0) ) } + + self.testcase("Overlapping Keyboard Frame With Additional Insets") { + listView.behavior.keyboardAdjustmentAdditionalInsets = UIEdgeInsets(top: 1, left: 2, bottom: 50, right: 4) + + let insets = listView.calculateScrollViewInsets( + with:.overlapping(frame: CGRect(x: 0, y: 200, width: 200, height: 200)) + ) + + XCTAssertEqual( + insets.content, + UIEdgeInsets(top: 1, left: 2, bottom: 250, right: 4) + ) + + XCTAssertEqual( + insets.horizontalScroll, + UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 40) + ) + + XCTAssertEqual( + insets.verticalScroll, + UIEdgeInsets(top: 10, left: 0, bottom: 250, right: 0) + ) + } + + self.testcase("Non-Overlapping Keyboard Frame With Additional Insets") { + let insets = listView.calculateScrollViewInsets(with: .nonOverlapping) + + XCTAssertEqual( + insets.content, + UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + ) + + XCTAssertEqual( + insets.verticalScroll, + UIEdgeInsets(top: 10, left: 0, bottom: 30, right: 0) + ) + } } func test_change_size() { From a7655902e196ea7d94a712fd0b979b8cc899effc Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 12:08:05 -0500 Subject: [PATCH 2/7] debug: log keyboard avoidance flow --- .../Sources/ListView/ListView.Delegate.swift | 4 ++ ListableUI/Sources/ListView/ListView.swift | 64 ++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/ListableUI/Sources/ListView/ListView.Delegate.swift b/ListableUI/Sources/ListView/ListView.Delegate.swift index e0ad9c92..2e4795f6 100644 --- a/ListableUI/Sources/ListView/ListView.Delegate.swift +++ b/ListableUI/Sources/ListView/ListView.Delegate.swift @@ -366,6 +366,10 @@ extension ListView func scrollViewDidScroll(_ scrollView: UIScrollView) { guard scrollView.bounds.size.height > 0 else { return } + + self.view.debugKeyboardAvoidance( + "scrollViewDidScroll contentOffset=\(scrollView.contentOffset) contentInset=\(scrollView.contentInset) adjustedInset=\(scrollView.adjustedContentInset) visibleFrame=\(scrollView.visibleContentFrame) contentSize=\(scrollView.contentSize)" + ) SignpostLogger.log(.begin, log: .scrollView, name: "scrollViewDidScroll", for: self.view) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index 57ddb814..043e79e9 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -191,6 +191,10 @@ public final class ListView : UIView private let keyboardObserver : KeyboardObserver private var lastKeyboardFrame : KeyboardFrame? = nil + + func debugKeyboardAvoidance(_ message: @autoclosure () -> String) { + print("[MRKT-86][Listable][\(debuggingIdentifier ?? "unnamed")] \(message())") + } // // MARK: Debugging @@ -416,6 +420,10 @@ public final class ListView : UIView /// whenever insets require an update. public func updateScrollViewInsets() { + let previousContentInset = self.collectionView.contentInset + let previousAdjustedContentInset = self.collectionView.adjustedContentInset + let previousContentOffset = self.collectionView.contentOffset + let insets: ScrollViewInsets if case .custom = self.behavior.keyboardAdjustmentMode { insets = self.customScrollViewInsets() @@ -436,6 +444,16 @@ public final class ListView : UIView if self.collectionView.verticalScrollIndicatorInsets != insets.verticalScroll { self.collectionView.verticalScrollIndicatorInsets = insets.verticalScroll } + + let nextAdjustedContentInset = self.collectionView.adjustedContentInset + if previousContentInset != self.collectionView.contentInset || + previousAdjustedContentInset != nextAdjustedContentInset || + previousContentOffset != self.collectionView.contentOffset + { + debugKeyboardAvoidance( + "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" + ) + } } func calculateScrollViewInsets(with keyboardFrame : KeyboardFrame?) -> ScrollViewInsets { @@ -471,6 +489,10 @@ public final class ListView : UIView ? self.behavior.keyboardAdjustmentAdditionalInsets : .zero + debugKeyboardAvoidance( + "calculateScrollViewInsets keyboardFrame=\(String(describing: keyboardFrame)) keyboardBottomInset=\(keyboardBottomInset) appliedAdditional=\(keyboardAdjustmentAdditionalInsets) wantsKeyboardInsetAdjustment=\(layout.wantsKeyboardInsetAdjustment) bounds=\(bounds)" + ) + let scrollInsets = modified(self.scrollIndicatorInsets) { $0.bottom = max($0.bottom, keyboardBottomInset + keyboardAdjustmentAdditionalInsets.bottom) } @@ -565,9 +587,14 @@ public final class ListView : UIView completion: ScrollCompletion? = nil ) -> Bool { + debugKeyboardAvoidance( + "scrollTo item=\(item) position=\(position) animated=\(animated) contentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame) adjustedInset=\(collectionView.adjustedContentInset)" + ) + // Make sure the item identifier is valid. guard let toIndexPath = self.storage.allContent.firstIndexPathForItem(with: item) else { + debugKeyboardAvoidance("scrollTo item=\(item) failed: missing item") handleScrollCompletion(reason: .cannotScroll, completion: completion) return false } @@ -589,6 +616,10 @@ public final class ListView : UIView let viewport = self.collectionView.visibleContentFrame let isAlreadyVisible = viewport.contains(itemFrame) + self.debugKeyboardAvoidance( + "scrollTo item=\(item) indexPath=\(toIndexPath) itemFrame=\(itemFrame) viewport=\(viewport) isAlreadyVisible=\(isAlreadyVisible) adjustedInset=\(self.collectionView.adjustedContentInset)" + ) + // If the item is already visible and that's good enough, return. if isAlreadyVisible && position.ifAlreadyVisible == .doNothing { @@ -666,12 +697,16 @@ public final class ListView : UIView completion: ScrollCompletion? = nil ) -> Bool { + debugKeyboardAvoidance( + "scrollToSection id=\(identifier) sectionPosition=\(sectionPosition) scrollPosition=\(scrollPosition) animated=\(animated) contentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame)" + ) let storageContent = storage.allContent // Make sure the section identifier is valid. guard let sectionIndex = storageContent.firstIndexForSection(with: identifier) else { + debugKeyboardAvoidance("scrollToSection id=\(identifier) failed: missing section") self.handleScrollCompletion(reason: .cannotScroll, completion: completion) return false } @@ -1136,9 +1171,16 @@ public final class ListView : UIView override public func layoutSubviews() { super.layoutSubviews() - + + let previousFrame = self.collectionView.frame self.collectionView.frame = self.bounds - + + if previousFrame != self.collectionView.frame { + debugKeyboardAvoidance( + "layoutSubviews collectionFrame \(previousFrame) -> \(collectionView.frame) bounds=\(bounds) contentOffset=\(collectionView.contentOffset) adjustedInset=\(collectionView.adjustedContentInset)" + ) + } + /// Our layout changed, update the keyboard inset in case the inset should now be different. self.updateScrollViewInsets() } @@ -1152,6 +1194,10 @@ public final class ListView : UIView guard let field = notification.object as? UIView else { return } + + debugKeyboardAvoidance( + "textDidBeginEditing field=\(type(of: field)) fieldFrameInList=\(field.convert(field.bounds, to: self)) contentOffset=\(collectionView.contentOffset) adjustedInset=\(collectionView.adjustedContentInset) visibleFrame=\(collectionView.visibleContentFrame)" + ) if let containingSupplementaryView = field.firstSuperview(ofType: SupplementaryContainerView.self) { containingSupplementaryView.headerFooter?.containsFirstResponder = true @@ -1527,6 +1573,10 @@ public final class ListView : UIView animated: Bool = false, completion: ScrollCompletion? = nil ) { + debugKeyboardAvoidance( + "performScroll targetFrame=\(targetFrame) scrollPosition=\(scrollPosition) animated=\(animated) currentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame) adjustedInset=\(collectionView.adjustedContentInset)" + ) + // If the item is already visible and that's good enough, return. let isAlreadyVisible = collectionView.visibleContentFrame.contains(targetFrame) @@ -1571,9 +1621,13 @@ public final class ListView : UIView y: round(collectionView.contentOffset.y) ) if roundedCurrentOffset != roundedResultOffset { + debugKeyboardAvoidance( + "performScroll setContentOffset result=\(resultOffset) roundedResult=\(roundedResultOffset) maxOffsetHeight=\(maxOffsetHeight) contentSize=\(collectionViewLayout.collectionViewContentSize)" + ) collectionView.setContentOffset(resultOffset, animated: shouldAnimate) handleScrollCompletion(reason: .scrolled(animated: shouldAnimate), completion: completion) } else { + debugKeyboardAvoidance("performScroll no-op roundedCurrent=\(roundedCurrentOffset) roundedResult=\(roundedResultOffset)") handleScrollCompletion(reason: .cannotScroll, completion: completion) } } @@ -1756,13 +1810,19 @@ extension ListView : KeyboardObserverDelegate public func keyboardFrameWillChange(for observer: KeyboardObserver, animationDuration: Double, animationCurve: UIView.AnimationCurve) { guard let frame = self.keyboardObserver.currentFrame(in: self) else { + debugKeyboardAvoidance("keyboardFrameWillChange skipped: no current frame duration=\(animationDuration) curve=\(animationCurve)") return } guard self.lastKeyboardFrame != frame else { + debugKeyboardAvoidance("keyboardFrameWillChange skipped: unchanged frame=\(frame)") return } + debugKeyboardAvoidance( + "keyboardFrameWillChange frame=\(frame) previous=\(String(describing: lastKeyboardFrame)) duration=\(animationDuration) curve=\(animationCurve) mode=\(behavior.keyboardAdjustmentMode) contentOffset=\(collectionView.contentOffset) contentInset=\(collectionView.contentInset) adjustedInset=\(collectionView.adjustedContentInset)" + ) + self.lastKeyboardFrame = frame if .custom != behavior.keyboardAdjustmentMode { From 985bbae75b082c7687f1e372064830aecefe703b Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 13:27:22 -0500 Subject: [PATCH 3/7] fix: relayout when scroll view insets change --- ListableUI/Sources/ListView/ListView.swift | 28 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index 043e79e9..bdaeb40b 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -127,6 +127,13 @@ public final class ListView : UIView name: UITextField.textDidBeginEditingNotification, object: nil ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(textDidBeginEditingNotification(_:)), + name: UITextView.textDidBeginEditingNotification, + object: nil + ) NotificationCenter.default.addObserver( self, @@ -134,6 +141,13 @@ public final class ListView : UIView name: UITextField.textDidEndEditingNotification, object: nil ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(textDidEndEditingNotification(_:)), + name: UITextView.textDidEndEditingNotification, + object: nil + ) } deinit @@ -446,12 +460,20 @@ public final class ListView : UIView } let nextAdjustedContentInset = self.collectionView.adjustedContentInset - if previousContentInset != self.collectionView.contentInset || - previousAdjustedContentInset != nextAdjustedContentInset || + let didChangeInsets = + previousContentInset != self.collectionView.contentInset || + previousAdjustedContentInset != nextAdjustedContentInset + + if didChangeInsets { + self.collectionViewLayout.setNeedsRelayout() + self.collectionView.layoutIfNeeded() + } + + if didChangeInsets || previousContentOffset != self.collectionView.contentOffset { debugKeyboardAvoidance( - "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" + "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) forcedRelayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" ) } } From 364e2bae6cf2d5ba693ea4266abd56b3aa1e77a6 Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 13:35:11 -0500 Subject: [PATCH 4/7] fix: avoid eager layout during inset animation --- ListableUI/Sources/ListView/ListView.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index bdaeb40b..12602b1b 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -466,14 +466,13 @@ public final class ListView : UIView if didChangeInsets { self.collectionViewLayout.setNeedsRelayout() - self.collectionView.layoutIfNeeded() } if didChangeInsets || previousContentOffset != self.collectionView.contentOffset { debugKeyboardAvoidance( - "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) forcedRelayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" + "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) invalidatedLayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" ) } } From d1e8c4e10ef750b09b02d667949682aa871f345a Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 13:41:44 -0500 Subject: [PATCH 5/7] fix: restore synchronous inset relayout --- ListableUI/Sources/ListView/ListView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index 12602b1b..bdaeb40b 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -466,13 +466,14 @@ public final class ListView : UIView if didChangeInsets { self.collectionViewLayout.setNeedsRelayout() + self.collectionView.layoutIfNeeded() } if didChangeInsets || previousContentOffset != self.collectionView.contentOffset { debugKeyboardAvoidance( - "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) invalidatedLayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" + "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) forcedRelayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" ) } } From deec8cd7478e0a97807bbab83d881118d8071c58 Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 17:25:25 -0500 Subject: [PATCH 6/7] fix: promote keyboard focus scroll animation --- ListableUI/Sources/ListView/ListView.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index bdaeb40b..ed9efd66 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -2085,6 +2085,14 @@ final class CollectionView : ListView.IOS16_4_First_Responder_Bug_CollectionView } } + override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { + // UIKit can ask for a non-animated focus scroll from inside the keyboard animation + // transaction. Promote that case to the scroll view's animated path so cells move + // through UICollectionView's normal scrolling lifecycle instead of jumping mid-transaction. + let shouldPromoteToScrollAnimation = !animated && UIView.inheritedAnimationDuration > 0 + super.scrollRectToVisible(rect, animated: animated || shouldPromoteToScrollAnimation) + } + /// Returns true when the content size is large enough that scrolling is possible /// without bouncing back to it's original position. var isContentScrollable: Bool { From 11eb3d4d929e5eb74f4c005a849c7093288576db Mon Sep 17 00:00:00 2001 From: Rob MacEachern Date: Fri, 15 May 2026 23:27:39 -0500 Subject: [PATCH 7/7] chore: remove keyboard debug logging --- .../Sources/ListView/ListView.Delegate.swift | 4 -- ListableUI/Sources/ListView/ListView.swift | 56 ------------------- 2 files changed, 60 deletions(-) diff --git a/ListableUI/Sources/ListView/ListView.Delegate.swift b/ListableUI/Sources/ListView/ListView.Delegate.swift index 2e4795f6..e0ad9c92 100644 --- a/ListableUI/Sources/ListView/ListView.Delegate.swift +++ b/ListableUI/Sources/ListView/ListView.Delegate.swift @@ -366,10 +366,6 @@ extension ListView func scrollViewDidScroll(_ scrollView: UIScrollView) { guard scrollView.bounds.size.height > 0 else { return } - - self.view.debugKeyboardAvoidance( - "scrollViewDidScroll contentOffset=\(scrollView.contentOffset) contentInset=\(scrollView.contentInset) adjustedInset=\(scrollView.adjustedContentInset) visibleFrame=\(scrollView.visibleContentFrame) contentSize=\(scrollView.contentSize)" - ) SignpostLogger.log(.begin, log: .scrollView, name: "scrollViewDidScroll", for: self.view) diff --git a/ListableUI/Sources/ListView/ListView.swift b/ListableUI/Sources/ListView/ListView.swift index ed9efd66..b53f8c0f 100644 --- a/ListableUI/Sources/ListView/ListView.swift +++ b/ListableUI/Sources/ListView/ListView.swift @@ -205,10 +205,6 @@ public final class ListView : UIView private let keyboardObserver : KeyboardObserver private var lastKeyboardFrame : KeyboardFrame? = nil - - func debugKeyboardAvoidance(_ message: @autoclosure () -> String) { - print("[MRKT-86][Listable][\(debuggingIdentifier ?? "unnamed")] \(message())") - } // // MARK: Debugging @@ -436,7 +432,6 @@ public final class ListView : UIView { let previousContentInset = self.collectionView.contentInset let previousAdjustedContentInset = self.collectionView.adjustedContentInset - let previousContentOffset = self.collectionView.contentOffset let insets: ScrollViewInsets if case .custom = self.behavior.keyboardAdjustmentMode { @@ -468,14 +463,6 @@ public final class ListView : UIView self.collectionViewLayout.setNeedsRelayout() self.collectionView.layoutIfNeeded() } - - if didChangeInsets || - previousContentOffset != self.collectionView.contentOffset - { - debugKeyboardAvoidance( - "updateScrollViewInsets mode=\(behavior.keyboardAdjustmentMode) didChangeInsets=\(didChangeInsets) forcedRelayout=\(didChangeInsets) bounds=\(bounds) safeArea=\(safeAreaInsets) previousContentInset=\(previousContentInset) nextContentInset=\(collectionView.contentInset) previousAdjusted=\(previousAdjustedContentInset) nextAdjusted=\(nextAdjustedContentInset) previousOffset=\(previousContentOffset) nextOffset=\(collectionView.contentOffset) additional=\(behavior.keyboardAdjustmentAdditionalInsets)" - ) - } } func calculateScrollViewInsets(with keyboardFrame : KeyboardFrame?) -> ScrollViewInsets { @@ -511,10 +498,6 @@ public final class ListView : UIView ? self.behavior.keyboardAdjustmentAdditionalInsets : .zero - debugKeyboardAvoidance( - "calculateScrollViewInsets keyboardFrame=\(String(describing: keyboardFrame)) keyboardBottomInset=\(keyboardBottomInset) appliedAdditional=\(keyboardAdjustmentAdditionalInsets) wantsKeyboardInsetAdjustment=\(layout.wantsKeyboardInsetAdjustment) bounds=\(bounds)" - ) - let scrollInsets = modified(self.scrollIndicatorInsets) { $0.bottom = max($0.bottom, keyboardBottomInset + keyboardAdjustmentAdditionalInsets.bottom) } @@ -609,14 +592,9 @@ public final class ListView : UIView completion: ScrollCompletion? = nil ) -> Bool { - debugKeyboardAvoidance( - "scrollTo item=\(item) position=\(position) animated=\(animated) contentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame) adjustedInset=\(collectionView.adjustedContentInset)" - ) - // Make sure the item identifier is valid. guard let toIndexPath = self.storage.allContent.firstIndexPathForItem(with: item) else { - debugKeyboardAvoidance("scrollTo item=\(item) failed: missing item") handleScrollCompletion(reason: .cannotScroll, completion: completion) return false } @@ -638,10 +616,6 @@ public final class ListView : UIView let viewport = self.collectionView.visibleContentFrame let isAlreadyVisible = viewport.contains(itemFrame) - self.debugKeyboardAvoidance( - "scrollTo item=\(item) indexPath=\(toIndexPath) itemFrame=\(itemFrame) viewport=\(viewport) isAlreadyVisible=\(isAlreadyVisible) adjustedInset=\(self.collectionView.adjustedContentInset)" - ) - // If the item is already visible and that's good enough, return. if isAlreadyVisible && position.ifAlreadyVisible == .doNothing { @@ -719,16 +693,11 @@ public final class ListView : UIView completion: ScrollCompletion? = nil ) -> Bool { - debugKeyboardAvoidance( - "scrollToSection id=\(identifier) sectionPosition=\(sectionPosition) scrollPosition=\(scrollPosition) animated=\(animated) contentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame)" - ) - let storageContent = storage.allContent // Make sure the section identifier is valid. guard let sectionIndex = storageContent.firstIndexForSection(with: identifier) else { - debugKeyboardAvoidance("scrollToSection id=\(identifier) failed: missing section") self.handleScrollCompletion(reason: .cannotScroll, completion: completion) return false } @@ -1194,15 +1163,8 @@ public final class ListView : UIView { super.layoutSubviews() - let previousFrame = self.collectionView.frame self.collectionView.frame = self.bounds - if previousFrame != self.collectionView.frame { - debugKeyboardAvoidance( - "layoutSubviews collectionFrame \(previousFrame) -> \(collectionView.frame) bounds=\(bounds) contentOffset=\(collectionView.contentOffset) adjustedInset=\(collectionView.adjustedContentInset)" - ) - } - /// Our layout changed, update the keyboard inset in case the inset should now be different. self.updateScrollViewInsets() } @@ -1216,10 +1178,6 @@ public final class ListView : UIView guard let field = notification.object as? UIView else { return } - - debugKeyboardAvoidance( - "textDidBeginEditing field=\(type(of: field)) fieldFrameInList=\(field.convert(field.bounds, to: self)) contentOffset=\(collectionView.contentOffset) adjustedInset=\(collectionView.adjustedContentInset) visibleFrame=\(collectionView.visibleContentFrame)" - ) if let containingSupplementaryView = field.firstSuperview(ofType: SupplementaryContainerView.self) { containingSupplementaryView.headerFooter?.containsFirstResponder = true @@ -1595,10 +1553,6 @@ public final class ListView : UIView animated: Bool = false, completion: ScrollCompletion? = nil ) { - debugKeyboardAvoidance( - "performScroll targetFrame=\(targetFrame) scrollPosition=\(scrollPosition) animated=\(animated) currentOffset=\(collectionView.contentOffset) visibleFrame=\(collectionView.visibleContentFrame) adjustedInset=\(collectionView.adjustedContentInset)" - ) - // If the item is already visible and that's good enough, return. let isAlreadyVisible = collectionView.visibleContentFrame.contains(targetFrame) @@ -1643,13 +1597,9 @@ public final class ListView : UIView y: round(collectionView.contentOffset.y) ) if roundedCurrentOffset != roundedResultOffset { - debugKeyboardAvoidance( - "performScroll setContentOffset result=\(resultOffset) roundedResult=\(roundedResultOffset) maxOffsetHeight=\(maxOffsetHeight) contentSize=\(collectionViewLayout.collectionViewContentSize)" - ) collectionView.setContentOffset(resultOffset, animated: shouldAnimate) handleScrollCompletion(reason: .scrolled(animated: shouldAnimate), completion: completion) } else { - debugKeyboardAvoidance("performScroll no-op roundedCurrent=\(roundedCurrentOffset) roundedResult=\(roundedResultOffset)") handleScrollCompletion(reason: .cannotScroll, completion: completion) } } @@ -1832,19 +1782,13 @@ extension ListView : KeyboardObserverDelegate public func keyboardFrameWillChange(for observer: KeyboardObserver, animationDuration: Double, animationCurve: UIView.AnimationCurve) { guard let frame = self.keyboardObserver.currentFrame(in: self) else { - debugKeyboardAvoidance("keyboardFrameWillChange skipped: no current frame duration=\(animationDuration) curve=\(animationCurve)") return } guard self.lastKeyboardFrame != frame else { - debugKeyboardAvoidance("keyboardFrameWillChange skipped: unchanged frame=\(frame)") return } - debugKeyboardAvoidance( - "keyboardFrameWillChange frame=\(frame) previous=\(String(describing: lastKeyboardFrame)) duration=\(animationDuration) curve=\(animationCurve) mode=\(behavior.keyboardAdjustmentMode) contentOffset=\(collectionView.contentOffset) contentInset=\(collectionView.contentInset) adjustedInset=\(collectionView.adjustedContentInset)" - ) - self.lastKeyboardFrame = frame if .custom != behavior.keyboardAdjustmentMode {