Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion ios/PagerScrollDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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> = [
#selector(scrollViewDidScroll(_:)),
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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
}
}

12 changes: 12 additions & 0 deletions ios/PagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,15 +52,20 @@ 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
}
.onChange(of: props.keyboardDismissMode) { newValue in
collectionView?.keyboardDismissMode = newValue
}
.onChange(of: props.layoutDirection) { newValue in
scrollDelegate.layoutDirection = newValue
}
}
}
Loading