-
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?
Changes from all commits
5bc4090
3793de6
ae5783a
b50f725
475536e
6c16f71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import Foundation | ||
| import SwiftUI | ||
|
|
||
| /** | ||
| Helper used to render UIViewController inside of SwiftUI. | ||
| This solves issues in some cases that can't found root UINavigationController. | ||
| */ | ||
| 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 makeUIViewController(context: Context) -> UIViewController { | ||
| let contentVC = UIViewController() | ||
| contentVC.view.backgroundColor = .clear | ||
| contentVC.view.addSubview(view) | ||
|
|
||
| return contentVC | ||
| } | ||
|
|
||
| #endif | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,12 +2,15 @@ import React | |||||||||||||||||||||||||||||||||||||||||||||
| import SwiftUI | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @available(iOS 18, macOS 15, visionOS 2, tvOS 18, *) | ||||||||||||||||||||||||||||||||||||||||||||||
| struct NewTabView: AnyTabView { | ||||||||||||||||||||||||||||||||||||||||||||||
| struct NewTabView: AnyTabView { | ||||||||||||||||||||||||||||||||||||||||||||||
| @ObservedObject var props: TabViewProps | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| var onLayout: (CGSize) -> Void | ||||||||||||||||||||||||||||||||||||||||||||||
| var onSelect: (String) -> Void | ||||||||||||||||||||||||||||||||||||||||||||||
| var onSearchTextChange: ((String) -> Void) | ||||||||||||||||||||||||||||||||||||||||||||||
| var onSearchFocusChange: ((Bool) -> Void) | ||||||||||||||||||||||||||||||||||||||||||||||
| var updateTabBarAppearance: () -> Void | ||||||||||||||||||||||||||||||||||||||||||||||
| @FocusState var focused: Bool | ||||||||||||||||||||||||||||||||||||||||||||||
| @State var query = "" | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+13
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @ViewBuilder | ||||||||||||||||||||||||||||||||||||||||||||||
| var body: some View { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,10 +32,33 @@ struct NewTabView: AnyTabView { | |||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Tab(value: tabData.key, role: tabData.role?.convert()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| RepresentableView(view: child.view) | ||||||||||||||||||||||||||||||||||||||||||||||
| .ignoresSafeArea(.container, edges: .all) | ||||||||||||||||||||||||||||||||||||||||||||||
| .tabAppear(using: context) | ||||||||||||||||||||||||||||||||||||||||||||||
| .hideTabBar(props.tabBarHidden) | ||||||||||||||||||||||||||||||||||||||||||||||
| //Have to wrap in NavigationView to use searchable | ||||||||||||||||||||||||||||||||||||||||||||||
| if(tabData.searchable){ | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| if(tabData.searchable){ | |
| if tabData.searchable { |
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. |
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 .searchFocused($focused) modifier is applied to RepresentableViewController but should be applied to the NavigationView or a level that encompasses the searchable content. The current placement may not work as expected with the search functionality.
| .searchFocused($focused) | |
| .onChange(of: focused){ newValue in | |
| onSearchFocusChange(newValue) | |
| } | |
| .onChange(of: query) { newValue in | |
| onSearchTextChange(newValue) | |
| } | |
| }.navigationViewStyle(StackNavigationViewStyle()) | |
| .searchable(text: $query) | |
| } | |
| .navigationViewStyle(StackNavigationViewStyle()) | |
| .searchable(text: $query) | |
| .searchFocused($focused) | |
| .onChange(of: focused){ newValue in | |
| onSearchFocusChange(newValue) | |
| } | |
| .onChange(of: query) { newValue in | |
| onSearchTextChange(newValue) | |
| } |
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 else keyword. Should format as } else { on a single line or properly indent the else block to match Swift formatting conventions.
| }else{ | |
| } else { |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,8 @@ public final class TabInfo: NSObject { | |||||||||||||
| public let testID: String? | ||||||||||||||
| public let role: TabBarRole? | ||||||||||||||
| public let preventsDefault: Bool | ||||||||||||||
|
|
||||||||||||||
| public let searchable: Bool | ||||||||||||||
| public let navigationBarToolbarStyle: ToolbarStyle | ||||||||||||||
| public init( | ||||||||||||||
| key: String, | ||||||||||||||
| title: String, | ||||||||||||||
|
|
@@ -23,7 +24,9 @@ public final class TabInfo: NSObject { | |||||||||||||
| hidden: Bool, | ||||||||||||||
| testID: String?, | ||||||||||||||
| role: String?, | ||||||||||||||
| preventsDefault: Bool = false | ||||||||||||||
| preventsDefault: Bool = false, | ||||||||||||||
| searchable:Bool = false, | ||||||||||||||
|
||||||||||||||
| searchable:Bool = false, | |
| searchable: Bool = false, |
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? |
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? |
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 closure parameter. Should be onSearchTextChange : { text in have consistent spacing as onSearchTextChange: { text in to match Swift formatting conventions.
| } onSearchTextChange : { text in | |
| } onSearchTextChange: { text in |
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.
Using rootTag instead of reactTag parameter. This is inconsistent with other delegate methods and should use the reactTag parameter passed to the closure for consistency.
| self.delegate?.onSearchFocusChange(isFocused: isFocused, reactTag: self.rootTag) | |
| self.delegate?.onSearchFocusChange(isFocused: isFocused, reactTag: self.reactTag) |
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.