diff --git a/ios/PagerScrollDelegate.swift b/ios/PagerScrollDelegate.swift index 962ee7b8..4e7f24ea 100644 --- a/ios/PagerScrollDelegate.swift +++ b/ios/PagerScrollDelegate.swift @@ -3,10 +3,14 @@ import UIKit /** Scroll delegate used to control underlying TabView's collection view. */ -class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDelegate { +class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDelegate, UIGestureRecognizerDelegate { weak var originalDelegate: UICollectionViewDelegate? weak var delegate: PagerViewProviderDelegate? var orientation: UICollectionView.ScrollDirection = .horizontal + weak var collectionView: UICollectionView? + var currentPage: Int = 0 + var layoutDirection: PagerLayoutDirection = .ltr + var scrollEnabled: Bool = true private let handledSelectors: Set = [ #selector(scrollViewDidScroll(_:)), @@ -46,6 +50,10 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { delegate?.onPageScrollStateChanged(state: .idle) + // Re-enable pan gesture recognizer after decelerating ends + if let collectionView = collectionView, !collectionView.panGestureRecognizer.isEnabled { + collectionView.panGestureRecognizer.isEnabled = scrollEnabled + } originalDelegate?.scrollViewDidEndDecelerating?(scrollView) } @@ -58,6 +66,10 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg if !decelerate { delegate?.onPageScrollStateChanged(state: .idle) } + // Re-enable pan gesture recognizer after dragging ends + if let collectionView = collectionView, !collectionView.panGestureRecognizer.isEnabled { + collectionView.panGestureRecognizer.isEnabled = scrollEnabled + } originalDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) } @@ -76,5 +88,45 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg override func forwardingTarget(for aSelector: Selector!) -> Any? { handledSelectors.contains(aSelector) ? nil : originalDelegate } + + // MARK: - UIGestureRecognizerDelegate + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + // iOS 26+ full-screen back gesture (interactiveContentPopGestureRecognizer) + if #available(iOS 18.2, *) { + guard let collectionView = collectionView, + let viewController = collectionView.next as? UIViewController, + let navigationController = viewController.navigationController else { + return false + } + + // Check if otherGestureRecognizer is the new iOS 26 interactiveContentPopGestureRecognizer + // NOTE: Using KVC to access private API 'interactiveContentPopGestureRecognizer' which is not + // yet publicly documented. The responds(to:) check ensures this won't crash if the API changes. + if gestureRecognizer == collectionView.panGestureRecognizer, + otherGestureRecognizer == navigationController.interactivePopGestureRecognizer || + (navigationController.responds(to: Selector(("interactiveContentPopGestureRecognizer"))) && + otherGestureRecognizer == navigationController.value(forKey: "interactiveContentPopGestureRecognizer") as? UIGestureRecognizer) { + + guard let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { + return false + } + + let velocity = panGestureRecognizer.velocity(in: collectionView) + let isLTR = layoutDirection == .ltr + let isBackGesture = (isLTR && velocity.x > 0) || (!isLTR && velocity.x < 0) + + if currentPage == 0 && isBackGesture { + collectionView.panGestureRecognizer.isEnabled = false + } else { + collectionView.panGestureRecognizer.isEnabled = scrollEnabled + } + + return true + } + } + + return false + } } diff --git a/ios/PagerView.swift b/ios/PagerView.swift index 4b866290..ad5b8e41 100644 --- a/ios/PagerView.swift +++ b/ios/PagerView.swift @@ -36,6 +36,13 @@ struct PagerView: View { scrollDelegate.delegate = delegate scrollDelegate.orientation = props.orientation collectionView.delegate = scrollDelegate + + // Set up gesture recognizer delegate for iOS 26 back gesture support + scrollDelegate.collectionView = collectionView + scrollDelegate.currentPage = props.currentPage + scrollDelegate.layoutDirection = props.layoutDirection + scrollDelegate.scrollEnabled = props.scrollEnabled + collectionView.panGestureRecognizer.delegate = scrollDelegate } } .onChange(of: props.children) { newValue in @@ -45,9 +52,11 @@ struct PagerView: View { } .onChange(of: props.currentPage) { newValue in delegate?.onPageSelected(position: newValue) + scrollDelegate.currentPage = newValue } .onChange(of: props.scrollEnabled) { newValue in collectionView?.isScrollEnabled = newValue + scrollDelegate.scrollEnabled = newValue } .onChange(of: props.overdrag) { newValue in collectionView?.bounces = newValue @@ -55,5 +64,8 @@ struct PagerView: View { .onChange(of: props.keyboardDismissMode) { newValue in collectionView?.keyboardDismissMode = newValue } + .onChange(of: props.layoutDirection) { newValue in + scrollDelegate.layoutDirection = newValue + } } }