-
Notifications
You must be signed in to change notification settings - Fork 82
feat: enhance bottom tabs with search functionality #493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds search functionality to the native bottom tabs component for iOS 18+. It introduces searchable tab capabilities with customizable navigation bar toolbar visibility and provides callbacks for search text and focus changes. The implementation uses SwiftUI's .searchable() modifier wrapped in a NavigationView to integrate search functionality with React Native views.
Key Changes:
- Added
searchableandnavigationBarToolbarStyleproperties to tab route options - Implemented
onSearchTextChangeandonSearchFocusChangecallbacks for search interactions - Created
RepresentableViewControllerwrapper to bridge UIViewController with SwiftUI's NavigationView
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
packages/react-navigation/src/views/NativeBottomTabView.tsx |
Added getters for searchable and navigationBarToolbarStyle options from route descriptors |
packages/react-navigation/src/types.ts |
Extended NativeBottomTabNavigationOptions with searchable and navigationBarToolbarStyle properties |
packages/react-navigation/src/navigators/createNativeBottomTabNavigator.tsx |
Passed through search callbacks to NativeBottomTabView |
packages/react-native-bottom-tabs/src/types.ts |
Added searchable and navigationBarToolbarStyle to BaseRoute type |
packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts |
Defined native event types and props for search functionality |
packages/react-native-bottom-tabs/src/TabView.tsx |
Implemented search callbacks and added getters for searchable properties |
packages/react-native-bottom-tabs/ios/TabViewProvider.swift |
Extended TabInfo with searchable properties and added delegate methods for search events |
packages/react-native-bottom-tabs/ios/TabViewProps.swift |
Added ToolbarStyle enum with iOS 18+ Visibility conversion |
packages/react-native-bottom-tabs/ios/TabViewImpl.swift |
Wired up search callbacks to native implementation |
packages/react-native-bottom-tabs/ios/TabView/NewTabView.swift |
Implemented searchable tabs with NavigationView wrapper and search modifiers |
packages/react-native-bottom-tabs/ios/RepresentableViewController.swift |
Created UIViewController wrapper for SwiftUI integration (new file) |
packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm |
Added native event emitters for search text and focus changes |
apps/example/src/Examples/NativeBottomTabsEmbeddedStacks.tsx |
Added example usage with searchable Contacts tab |
apps/example/src/Examples/FourTabs.tsx |
Added example usage with searchable Contacts tab |
apps/example/ios/Podfile.lock |
Updated package version from 1.0.5 to 1.1.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| //Have to wrap in NavigationView to use searchable | ||
| if(tabData.searchable){ | ||
| NavigationView{ | ||
| //If it is not wrapped in UIViewController, it will crash. |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "Have to wrap in NavigationView to use searchable" but doesn't explain why wrapping in UIViewController is required. The comment on line 38 mentions it will crash but doesn't provide context about what causes the crash or under what conditions.
| //Have to wrap in NavigationView to use searchable | |
| if(tabData.searchable){ | |
| NavigationView{ | |
| //If it is not wrapped in UIViewController, it will crash. | |
| // `.searchable` renders the search field in the navigation bar, so the content | |
| // must be placed inside a `NavigationView` for the search UI to appear correctly. | |
| if(tabData.searchable){ | |
| NavigationView{ | |
| // The React Native root view is a UIKit `UIView`. When used as the root content | |
| // of a `NavigationView` with `.searchable`, embedding the `UIView` directly | |
| // (without wrapping it in a `UIViewController`) causes a runtime crash on iOS. | |
| // `RepresentableViewController` wraps the `UIView` in a `UIViewController` | |
| // to satisfy SwiftUI's expectations for the navigation/search container. |
|
|
||
| // MARK: TabViewProviderDelegate | ||
|
|
||
| // MARK: TabViewProviderDelegate |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate comment line with inconsistent formatting. The comment "// MARK: TabViewProviderDelegate" has extra spaces before "MARK:" compared to standard Swift conventions which typically use "// MARK:" with a single space.
| // MARK: TabViewProviderDelegate | |
| // MARK: TabViewProviderDelegate |
| role: 'search', | ||
| searchable: true, | ||
| navigationBarToolbarStyle: | ||
| Platform.Version === 26 || Platform.Version === '26.0' |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version check uses Platform.Version === 26 || Platform.Version === '26.0' which checks for both number and string. On iOS, Platform.Version is always a string, so the numeric comparison === 26 will never be true. Only the string comparison is needed.
| Platform.Version === 26 || Platform.Version === '26.0' | |
| Platform.Version === '26.0' |
| * Get navigation bar toolbar style for the tab, uses `route.navigationBarToolbarStyle` by default. | ||
| */ | ||
|
|
||
| /** |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate comment for getNavigationBarToolbarStyle. Lines 139-141 describe the same getter that's already documented in lines 109-114. One of these duplicate comment blocks should be removed.
| * Get navigation bar toolbar style for the tab, uses `route.navigationBarToolbarStyle` by default. | |
| */ | |
| /** |
| export type OnChangeTextEventDataData = Readonly<{ | ||
| text: string; | ||
| }>; |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type name OnChangeTextEventDataData has "Data" repeated twice. This should be renamed to OnChangeTextEventData or OnSearchTextChangeEventData for consistency with other event data types like OnPageSelectedEventData, OnSearchBarFocusChangeData, etc.
| func makeUIView(context: Context) -> PlatformView { | ||
| let wrapper = UIView() | ||
| wrapper.addSubview(view) | ||
| return wrapper | ||
| } | ||
| func makeUIViewController(context: Context) -> UIViewController { | ||
| let contentVC = UIViewController() | ||
| contentVC.view.backgroundColor = .clear | ||
| contentVC.view.addSubview(view) | ||
|
|
||
| return contentVC | ||
| } | ||
| func updateUIView(_ uiView: PlatformView, context: Context) {} |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The makeUIView function is defined but never called for iOS builds. The protocol UIViewControllerRepresentable should only implement makeUIViewController and updateUIViewController on iOS. The makeUIView and updateUIView methods are for UIViewRepresentable, not UIViewControllerRepresentable.
| struct RepresentableViewController: UIViewControllerRepresentable { | ||
| func updateUIViewController(_ uiViewController: UIViewController, context: Context) { | ||
|
|
||
| } | ||
|
|
||
|
|
||
| var view: PlatformView | ||
|
|
||
| #if os(macOS) | ||
|
|
||
| func makeNSView(context: Context) -> PlatformView { | ||
| let wrapper = NSView() | ||
| wrapper.addSubview(view) | ||
| return wrapper | ||
| } | ||
|
|
||
| func updateNSView(_ nsView: PlatformView, context: Context) {} | ||
|
|
||
| #else | ||
|
|
||
| func makeUIView(context: Context) -> PlatformView { | ||
| let wrapper = UIView() | ||
| wrapper.addSubview(view) | ||
| return wrapper | ||
| } | ||
| func makeUIViewController(context: Context) -> UIViewController { | ||
| let contentVC = UIViewController() | ||
| contentVC.view.backgroundColor = .clear | ||
| contentVC.view.addSubview(view) | ||
|
|
||
| return contentVC | ||
| } | ||
| func updateUIView(_ uiView: PlatformView, context: Context) {} | ||
|
|
||
| #endif | ||
| } |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing implementation for macOS. The struct declares itself as UIViewControllerRepresentable which doesn't exist on macOS (should be NSViewControllerRepresentable). The macOS section implements view-related methods but the struct protocol is for view controllers.
| struct RepresentableViewController: UIViewControllerRepresentable { | |
| func updateUIViewController(_ uiViewController: UIViewController, context: Context) { | |
| } | |
| var view: PlatformView | |
| #if os(macOS) | |
| func makeNSView(context: Context) -> PlatformView { | |
| let wrapper = NSView() | |
| wrapper.addSubview(view) | |
| return wrapper | |
| } | |
| func updateNSView(_ nsView: PlatformView, context: Context) {} | |
| #else | |
| func makeUIView(context: Context) -> PlatformView { | |
| let wrapper = UIView() | |
| wrapper.addSubview(view) | |
| return wrapper | |
| } | |
| func makeUIViewController(context: Context) -> UIViewController { | |
| let contentVC = UIViewController() | |
| contentVC.view.backgroundColor = .clear | |
| contentVC.view.addSubview(view) | |
| return contentVC | |
| } | |
| func updateUIView(_ uiView: PlatformView, context: Context) {} | |
| #endif | |
| } | |
| #if os(macOS) | |
| struct RepresentableViewController: NSViewRepresentable { | |
| var view: PlatformView | |
| func makeNSView(context: Context) -> PlatformView { | |
| let wrapper = NSView() | |
| wrapper.addSubview(view) | |
| return wrapper | |
| } | |
| func updateNSView(_ nsView: PlatformView, context: Context) {} | |
| } | |
| #else | |
| struct RepresentableViewController: UIViewControllerRepresentable { | |
| var view: PlatformView | |
| func makeUIView(context: Context) -> PlatformView { | |
| let wrapper = UIView() | |
| wrapper.addSubview(view) | |
| return wrapper | |
| } | |
| func updateUIView(_ uiView: PlatformView, context: Context) {} | |
| func makeUIViewController(context: Context) -> UIViewController { | |
| let contentVC = UIViewController() | |
| contentVC.view.backgroundColor = .clear | |
| contentVC.view.addSubview(view) | |
| return contentVC | |
| } | |
| func updateUIViewController(_ uiViewController: UIViewController, context: Context) { | |
| } | |
| } | |
| #endif |
| @objc var onSearchTextChange : RCTDirectEventBlock? | ||
| @objc var onSearchFocusChange : RCTDirectEventBlock? |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after colon in variable declaration. Should be onSearchTextChange : RCTDirectEventBlock? have consistent spacing as onSearchTextChange: RCTDirectEventBlock? to match the style of other properties in this file.
| @objc var onSearchTextChange : RCTDirectEventBlock? | |
| @objc var onSearchFocusChange : RCTDirectEventBlock? | |
| @objc var onSearchTextChange: RCTDirectEventBlock? | |
| @objc var onSearchFocusChange: RCTDirectEventBlock? |
| @objc var onTabBarMeasured: RCTDirectEventBlock? | ||
| @objc var onNativeLayout: RCTDirectEventBlock? | ||
| @objc var onSearchTextChange : RCTDirectEventBlock? | ||
| @objc var onSearchFocusChange : RCTDirectEventBlock? |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after colon in variable declaration. Should be onSearchFocusChange : RCTDirectEventBlock? have consistent spacing as onSearchFocusChange: RCTDirectEventBlock? to match the style of other properties in this file.
| @objc var onSearchFocusChange : RCTDirectEventBlock? | |
| @objc var onSearchFocusChange: RCTDirectEventBlock? |
| tabBarIcon: () => require('../../assets/icons/person_dark.png'), | ||
| searchable: true, | ||
| navigationBarToolbarStyle: | ||
| Platform.Version === 26 || Platform.Version === '26.0' |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version check uses Platform.Version === 26 || Platform.Version === '26.0' which checks for both number and string. On iOS, Platform.Version is always a string, so the numeric comparison === 26 will never be true. Only the string comparison is needed.
| Platform.Version === 26 || Platform.Version === '26.0' | |
| Platform.Version === '26.0' |
searchableboolean property to route optionsnavigationBarToolbarStyleproperty to control navigation bar toolbar visibility ('automatic' | 'hidden' | 'visible')onSearchTextChangecallback that fires when search text changesonSearchFocusChangecallback that fires when search field gains/loses focusRepresentableViewControllerwrapper to properly integrate UIViewController with SwiftUI NavigationView (required for searchable functionality)NavigationViewand apply.searchable()modifierTabViewusage and React Navigation integrationHow to test?
Using TabView directly:
Using React Navigation:
Screenshots
For iOS 26 and higher


For iOS 18