Skip to content

Commit cb7013f

Browse files
CopilottroZee
andcommitted
Fix thread safety and improve API access pattern
Co-authored-by: troZee <12766071+troZee@users.noreply.github.com>
1 parent 57dab2d commit cb7013f

1 file changed

Lines changed: 57 additions & 50 deletions

File tree

ios/PagerGestureDelegate.swift

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,65 +12,72 @@ class PagerGestureDelegate: NSObject, UIGestureRecognizerDelegate {
1212
private var gestureObserver: NSKeyValueObservation?
1313

1414
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
15-
// iOS 26+ full-screen back gesture (interactiveContentPopGestureRecognizer)
15+
// Get the navigation controller
16+
guard let collectionView = collectionView,
17+
let viewController = collectionView.reactViewController(),
18+
let navigationController = viewController.navigationController else {
19+
return false
20+
}
21+
22+
// Check if this is the pager's pan gesture
23+
guard gestureRecognizer == collectionView.panGestureRecognizer else {
24+
return false
25+
}
26+
27+
// Determine which navigation gesture recognizer to check
28+
var navGestureRecognizer: UIGestureRecognizer? = navigationController.interactivePopGestureRecognizer
29+
30+
// iOS 26+ introduces interactiveContentPopGestureRecognizer for full-screen back gestures
1631
if #available(iOS 26, *) {
17-
// Get the navigation controller's interactive content pop gesture recognizer
18-
guard let collectionView = collectionView,
19-
let viewController = collectionView.reactViewController(),
20-
let navigationController = viewController.navigationController else {
21-
return false
22-
}
23-
24-
// iOS 26 introduces interactiveContentPopGestureRecognizer for full-screen back gestures
25-
// We need to check if this property exists and use it, otherwise fall back to standard behavior
26-
let interactiveGesture: UIGestureRecognizer?
27-
if let contentPopGesture = navigationController.value(forKey: "interactiveContentPopGestureRecognizer") as? UIGestureRecognizer {
28-
interactiveGesture = contentPopGesture
29-
} else {
30-
// Fallback to standard interactivePopGestureRecognizer
31-
interactiveGesture = navigationController.interactivePopGestureRecognizer
32+
// Try to access the new property safely using selector
33+
let selector = NSSelectorFromString("interactiveContentPopGestureRecognizer")
34+
if navigationController.responds(to: selector),
35+
let contentPopGesture = navigationController.perform(selector)?.takeUnretainedValue() as? UIGestureRecognizer {
36+
navGestureRecognizer = contentPopGesture
3237
}
38+
}
39+
40+
// Check if the other gesture is the navigation back gesture
41+
guard let navGesture = navGestureRecognizer,
42+
otherGestureRecognizer == navGesture else {
43+
return false
44+
}
45+
46+
// Get velocity to determine swipe direction
47+
guard let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
48+
return false
49+
}
50+
51+
let velocity = panGestureRecognizer.velocity(in: collectionView)
52+
let isLTR = layoutDirection == .ltr
53+
let isBackGesture = (isLTR && velocity.x > 0) || (!isLTR && velocity.x < 0)
54+
55+
// If on first page and performing back gesture, disable pager scroll to allow navigation
56+
if currentPage == 0 && isBackGesture {
57+
collectionView.panGestureRecognizer.isEnabled = false
3358

34-
// Check if this is the pager's pan gesture and the other is the navigation back gesture
35-
if gestureRecognizer == collectionView.panGestureRecognizer,
36-
otherGestureRecognizer == interactiveGesture {
59+
// Observe gesture state to re-enable when gesture ends
60+
gestureObserver?.invalidate()
61+
gestureObserver = otherGestureRecognizer.observe(\.state, options: [.new]) { [weak self, weak collectionView] _, change in
62+
guard let self = self,
63+
let collectionView = collectionView,
64+
let newState = change.newValue else { return }
3765

38-
// Get velocity to determine swipe direction
39-
guard let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
40-
return false
41-
}
42-
43-
let velocity = panGestureRecognizer.velocity(in: collectionView)
44-
let isLTR = layoutDirection == .ltr
45-
let isBackGesture = (isLTR && velocity.x > 0) || (!isLTR && velocity.x < 0)
46-
47-
// If on first page and performing back gesture, disable pager scroll to allow navigation
48-
if currentPage == 0 && isBackGesture {
49-
collectionView.panGestureRecognizer.isEnabled = false
50-
51-
// Observe gesture state to re-enable when gesture ends
52-
gestureObserver?.invalidate()
53-
gestureObserver = otherGestureRecognizer.observe(\.state, options: [.new]) { [weak self, weak collectionView] _, change in
54-
guard let self = self,
55-
let collectionView = collectionView,
56-
let newState = change.newValue else { return }
57-
58-
// Re-enable pager scroll when navigation gesture ends
59-
if newState == .ended || newState == .cancelled || newState == .failed {
60-
collectionView.panGestureRecognizer.isEnabled = self.scrollEnabled
61-
self.gestureObserver?.invalidate()
62-
self.gestureObserver = nil
63-
}
66+
// Re-enable pager scroll when navigation gesture ends
67+
if newState == .ended || newState == .cancelled || newState == .failed {
68+
// Ensure UIKit updates happen on main queue
69+
DispatchQueue.main.async {
70+
collectionView.panGestureRecognizer.isEnabled = self.scrollEnabled
6471
}
65-
} else {
66-
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
72+
self.gestureObserver?.invalidate()
73+
self.gestureObserver = nil
6774
}
68-
69-
return true
7075
}
76+
} else {
77+
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
7178
}
7279

73-
return false
80+
return true
7481
}
7582

7683
deinit {

0 commit comments

Comments
 (0)