The new SwiftUI framework does not seem to provide a built-in search bar component. Should I use a UISearchController and wrap it in some way, or should I use a simple textf
A native Search Bar can be properly implemented in SwiftUI by wrapping the UINavigationController.
This approach gives us the advantage of achieving all the expected behaviours including automatic hide/show on scroll, clear and cancel button, and search key in the keyboard among others.
Wrapping the UINavigationController for Search Bar also ensures that any new changes made to them by Apple are automatically adopted in your project.
Click here to see the implementation in action
import SwiftUI
struct SearchNavigation: UIViewControllerRepresentable {
@Binding var text: String
var search: () -> Void
var cancel: () -> Void
var content: () -> Content
func makeUIViewController(context: Context) -> UINavigationController {
let navigationController = UINavigationController(rootViewController: context.coordinator.rootViewController)
navigationController.navigationBar.prefersLargeTitles = true
context.coordinator.searchController.searchBar.delegate = context.coordinator
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
context.coordinator.update(content: content())
}
func makeCoordinator() -> Coordinator {
Coordinator(content: content(), searchText: $text, searchAction: search, cancelAction: cancel)
}
class Coordinator: NSObject, UISearchBarDelegate {
@Binding var text: String
let rootViewController: UIHostingController
let searchController = UISearchController(searchResultsController: nil)
var search: () -> Void
var cancel: () -> Void
init(content: Content, searchText: Binding, searchAction: @escaping () -> Void, cancelAction: @escaping () -> Void) {
rootViewController = UIHostingController(rootView: content)
searchController.searchBar.autocapitalizationType = .none
searchController.obscuresBackgroundDuringPresentation = false
rootViewController.navigationItem.searchController = searchController
_text = searchText
search = searchAction
cancel = cancelAction
}
func update(content: Content) {
rootViewController.rootView = content
rootViewController.view.setNeedsDisplay()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
search()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
cancel()
}
}
}
The above code can be used as-is (and can of-course be modified to suit the specific needs of the project).
The view includes actions for 'search' and 'cancel' which are respectively called when the search key is tapped on the keyboard and the cancel button of the search bar is pressed. The view also includes a SwiftUI view as a trailing closure and hence can directly replace the NavigationView.
import SwiftUI
struct YourView: View {
// Search string to use in the search bar
@State var searchString = ""
// Search action. Called when search key pressed on keyboard
func search() {
}
// Cancel action. Called when cancel button of search bar pressed
func cancel() {
}
// View body
var body: some View {
// Search Navigation. Can be used like a normal SwiftUI NavigationView.
SearchNavigation(text: $searchString, search: search, cancel: cancel) {
// Example SwiftUI View
List(dataArray) { data in
Text(data.text)
}
.navigationBarTitle("Usage Example")
}
.edgesIgnoringSafeArea(.top)
}
}
I have also written an article on this, it may be referred to get additional clarification.
I hope this helps, cheers!