@@ -7,9 +7,14 @@ import UIKit
77#if !os(macOS) && !os(visionOS)
88
99private final class TabBarDelegate : NSObject , UITabBarControllerDelegate {
10- var onClick : ( ( _ index: Int ) -> Bool ) ?
10+ var onClick : ( ( _ index: Int ? , _ identifier : String ? ) -> Bool ) ?
1111
1212 func tabBarController( _ tabBarController: UITabBarController , shouldSelect viewController: UIViewController ) -> Bool {
13+ if #available( iOS 27 . 0 , * ) {
14+ // iOS 27 routes SwiftUI TabView selection through shouldSelectTab.
15+ return true
16+ }
17+
1318#if os(iOS)
1419 // Handle "More" Tab
1520 if tabBarController. moreNavigationController == viewController {
@@ -21,7 +26,7 @@ private final class TabBarDelegate: NSObject, UITabBarControllerDelegate {
2126
2227 if isReselectingSameTab {
2328 if let index = tabBarController. viewControllers? . firstIndex ( of: viewController) {
24- _ = onClick ? ( index)
29+ _ = onClick ? ( index, nil )
2530 }
2631
2732 return false
@@ -31,17 +36,45 @@ private final class TabBarDelegate: NSObject, UITabBarControllerDelegate {
3136 // See: https://github.com/callstackincubator/react-native-bottom-tabs/issues/383
3237 // Due to this, whether the tab prevents default has to be defined statically.
3338 if let index = tabBarController. viewControllers? . firstIndex ( of: viewController) {
34- let defaultPrevented = onClick ? ( index) ?? false
39+ let defaultPrevented = onClick ? ( index, nil ) ?? false
3540
3641 return !defaultPrevented
3742 }
3843
3944 return false
4045 }
46+
47+ @available ( iOS 18 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
48+ func tabBarController( _ tabBarController: UITabBarController , shouldSelectTab tab: UITab ) -> Bool {
49+ guard #available( iOS 27 . 0 , * ) else {
50+ return true
51+ }
52+
53+ let isReselectingSameTab =
54+ tabBarController. selectedTab === tab ||
55+ tabBarController. selectedTab? . identifier == tab. identifier
56+
57+ // Unfortunately, due to iOS 26 new tab switching animations, controlling state from JavaScript is causing significant delays when switching tabs.
58+ // See: https://github.com/callstackincubator/react-native-bottom-tabs/issues/383
59+ // Due to this, whether the tab prevents default has to be defined statically.
60+ let defaultPrevented = onClick ? (
61+ tabIndex ( for: tab, in: tabBarController) ,
62+ tab. identifier
63+ ) ?? false
64+
65+ return isReselectingSameTab ? false : !defaultPrevented
66+ }
67+
68+ @available ( iOS 18 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
69+ private func tabIndex( for tab: UITab , in tabBarController: UITabBarController ) -> Int ? {
70+ tabBarController. tabs. firstIndex {
71+ $0 === tab || $0. identifier == tab. identifier
72+ }
73+ }
4174}
4275
4376struct TabItemEventModifier : ViewModifier {
44- let onTabEvent : ( _ key : Int , _ isLongPress: Bool ) -> Bool
77+ let onTabEvent : ( _ index : Int ? , _ identifier : String ? , _ isLongPress: Bool ) -> Bool
4578 private let delegate = TabBarDelegate ( )
4679
4780 func body( content: Content ) -> some View {
@@ -52,8 +85,8 @@ struct TabItemEventModifier: ViewModifier {
5285 }
5386
5487 func handle( tabController: UITabBarController ) {
55- delegate. onClick = { index in
56- onTabEvent ( index, false )
88+ delegate. onClick = { index, identifier in
89+ onTabEvent ( index, identifier , false )
5790 }
5891 tabController. delegate = delegate
5992
@@ -70,7 +103,7 @@ struct TabItemEventModifier: ViewModifier {
70103 }
71104
72105 // Create gesture handler
73- let handler = LongPressGestureHandler ( tabBar: tabController. tabBar) { key , isLongPress in _ = onTabEvent ( key , isLongPress) }
106+ let handler = LongPressGestureHandler ( tabBar: tabController. tabBar) { index , isLongPress in _ = onTabEvent ( index , nil , isLongPress) }
74107 let gesture = UILongPressGestureRecognizer ( target: handler, action: #selector( LongPressGestureHandler . handleLongPress ( _: ) ) )
75108 gesture. minimumPressDuration = 0.5
76109
@@ -122,7 +155,7 @@ extension View {
122155 /**
123156 Event for tab items. Returns true if should prevent default (switching tabs).
124157 */
125- func onTabItemEvent( _ handler: @escaping ( Int , Bool ) -> Bool ) -> some View {
158+ func onTabItemEvent( _ handler: @escaping ( Int ? , String ? , Bool ) -> Bool ) -> some View {
126159 modifier ( TabItemEventModifier ( onTabEvent: handler) )
127160 }
128161}
0 commit comments