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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json

# Expo
.expo/*
Expand Down
86 changes: 86 additions & 0 deletions ios/PagerGestureDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import UIKit

/**
Gesture recognizer delegate to handle iOS 26+ interactiveContentPopGestureRecognizer.
Allows navigation back gesture on first page while preserving pager functionality.
*/
class PagerGestureDelegate: NSObject, UIGestureRecognizerDelegate {
weak var collectionView: UICollectionView?
var currentPage: Int = 0
var scrollEnabled: Bool = true
var layoutDirection: PagerLayoutDirection = .ltr
private var gestureObserver: NSKeyValueObservation?

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Get the navigation controller
guard let collectionView = collectionView,
let viewController = collectionView.reactViewController(),
let navigationController = viewController.navigationController else {
return false
}

// Check if this is the pager's pan gesture
guard gestureRecognizer == collectionView.panGestureRecognizer else {
return false
}

// Determine which navigation gesture recognizer to check
var navGestureRecognizer: UIGestureRecognizer? = navigationController.interactivePopGestureRecognizer

// iOS 26+ introduces interactiveContentPopGestureRecognizer for full-screen back gestures
if #available(iOS 26, *) {
// Try to access the new property safely using selector
let selector = NSSelectorFromString("interactiveContentPopGestureRecognizer")
if navigationController.responds(to: selector),
let contentPopGesture = navigationController.perform(selector)?.takeUnretainedValue() as? UIGestureRecognizer {
navGestureRecognizer = contentPopGesture
}
}

// Check if the other gesture is the navigation back gesture
guard let navGesture = navGestureRecognizer,
otherGestureRecognizer == navGesture else {
return false
}

// Get velocity to determine swipe direction
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 on first page and performing back gesture, disable pager scroll to allow navigation
if currentPage == 0 && isBackGesture {
collectionView.panGestureRecognizer.isEnabled = false

// Observe gesture state to re-enable when gesture ends
gestureObserver?.invalidate()
gestureObserver = otherGestureRecognizer.observe(\.state, options: [.new]) { [weak self, weak collectionView] _, change in
guard let self = self,
let collectionView = collectionView,
let newState = change.newValue else { return }

// Re-enable pager scroll when navigation gesture ends
if newState == .ended || newState == .cancelled || newState == .failed {
// Ensure UIKit updates happen on main queue
DispatchQueue.main.async {
collectionView.panGestureRecognizer.isEnabled = self.scrollEnabled
}
self.gestureObserver?.invalidate()
self.gestureObserver = nil
}
}
} else {
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
}

return true
}

deinit {
gestureObserver?.invalidate()
}
}
13 changes: 13 additions & 0 deletions ios/PagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI
struct PagerView: View {
@ObservedObject var props: PagerViewProps
@State private var scrollDelegate = PagerScrollDelegate()
@State private var gestureDelegate = PagerGestureDelegate()
weak var delegate: PagerViewProviderDelegate?

@Weak var collectionView: UICollectionView?
Expand Down Expand Up @@ -37,6 +38,13 @@ struct PagerView: View {
scrollDelegate.orientation = props.orientation
collectionView.delegate = scrollDelegate
}

// Set up gesture delegate for iOS 26+ back gesture handling
gestureDelegate.collectionView = collectionView
gestureDelegate.currentPage = props.currentPage
gestureDelegate.scrollEnabled = props.scrollEnabled
gestureDelegate.layoutDirection = props.layoutDirection
collectionView.panGestureRecognizer.delegate = gestureDelegate
}
.onChange(of: props.children) { newValue in
if props.currentPage >= newValue.count && !newValue.isEmpty {
Expand All @@ -45,15 +53,20 @@ struct PagerView: View {
}
.onChange(of: props.currentPage) { newValue in
delegate?.onPageSelected(position: newValue)
gestureDelegate.currentPage = newValue
}
.onChange(of: props.scrollEnabled) { newValue in
collectionView?.isScrollEnabled = newValue
gestureDelegate.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
gestureDelegate.layoutDirection = newValue
}
}
}
Loading