diff --git a/ios/PagerScrollDelegate.swift b/ios/PagerScrollDelegate.swift index 5b09e032..ffb489f0 100644 --- a/ios/PagerScrollDelegate.swift +++ b/ios/PagerScrollDelegate.swift @@ -4,7 +4,7 @@ import UIKit Scroll delegate used to control underlying TabView's collection view. */ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDelegate { - weak var originalDelegate: UICollectionViewDelegate? + weak var originalDelegate: UIScrollViewDelegate? weak var delegate: PagerViewProviderDelegate? var orientation: UICollectionView.ScrollDirection = .horizontal @@ -78,11 +78,13 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - originalDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath) + (originalDelegate as? UICollectionViewDelegate)?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath) } func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - originalDelegate?.collectionView?(collectionView, willDisplay: cell, forItemAt: indexPath) + // iOS 15 only, see PagerViewIOS15Compat.swift (delete together with the iOS 15 support). + PagerViewIOS15Compat.attachScrollDelegate(to: collectionView, delegate: delegate) + (originalDelegate as? UICollectionViewDelegate)?.collectionView?(collectionView, willDisplay: cell, forItemAt: indexPath) } override func responds(to aSelector: Selector!) -> Bool { diff --git a/ios/PagerViewIOS15Compat.swift b/ios/PagerViewIOS15Compat.swift index 569033a7..763a803b 100644 --- a/ios/PagerViewIOS15Compat.swift +++ b/ios/PagerViewIOS15Compat.swift @@ -7,14 +7,18 @@ import UIKit /// collection view (private class `_UICollectionViewOrthogonalScrollerEmbeddedScrollView`), while /// the collection view itself only scrolls vertically. Because `PagerView` can only toggle the /// collection view, on iOS 15 `scrollEnabled={false}` / `overdrag={false}` never reached the view -/// that actually handles the swipe. +/// that actually handles the swipe. For the same reason `PagerScrollDelegate`, installed on the +/// collection view, never saw the paging scroll, so `onPageScroll` / `onPageScrollStateChanged` +/// were never emitted. /// /// On iOS 16 and later the collection view itself is the pager, so none of this is needed — the /// `#available` checks below make every function in here a no-op on those versions. /// /// - Note: Delete this file together with the two `PagerViewIOS15Compat` calls in `PagerView.swift` -/// once the minimum supported iOS version is 16. +/// and the one in `PagerScrollDelegate.swift` once the minimum supported iOS version is 16. enum PagerViewIOS15Compat { + private static var scrollDelegateKey: UInt8 = 0 + /// Mirrors `scrollEnabled` onto the scroll views SwiftUI embedded in the paging collection view. static func applyScrollEnabled(_ scrollEnabled: Bool, to collectionView: UICollectionView?) { if #available(iOS 16, *) { @@ -33,6 +37,23 @@ enum PagerViewIOS15Compat { pagingScrollViews(in: collectionView).forEach { $0.bounces = overdrag } } + /// Puts a `PagerScrollDelegate` in front of the delegate of the scroll views SwiftUI embedded in + /// the paging collection view. They only exist once the collection view has laid out its pages. + static func attachScrollDelegate(to collectionView: UICollectionView, delegate: PagerViewProviderDelegate?) { + if #available(iOS 16, *) { + return + } + + for scrollView in pagingScrollViews(in: collectionView) where !(scrollView.delegate is PagerScrollDelegate) { + let scrollDelegate = PagerScrollDelegate() + scrollDelegate.originalDelegate = scrollView.delegate + scrollDelegate.delegate = delegate + // `UIScrollView.delegate` is weak; the scroll view owns its delegate instead. + objc_setAssociatedObject(scrollView, &scrollDelegateKey, scrollDelegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + scrollView.delegate = scrollDelegate + } + } + /// Scroll views that SwiftUI embedded in the paging collection view. /// /// Scroll views that belong to the pages' React Native content live inside the paging cells, so