37
loading...
This website collects cookies to deliver better user experience
have I to use native widget or I’ve to implement a completely new widget from scratch using SwiftUi ?
@viewbuilder
) so we'll define the search bar and also the view where the search result will be applied.SearchBar(text: $searchText ) {
List {
ForEach( (1...50)
.map( { "Item\($0)" } )
.filter({ $0.starts(with: searchText)}), id: \.self) { item in
Text("\(item)")
}
}
}
UISearchBox
.UIKit
offers a pretty convenient way to implement a native search bar embedded into the navigation bar. In a typical UINavigationController
a navigation stack, each UIViewController
has a corresponding UINavigationItem
that has a property called searchController
.UISearchController
instance to a UINavigationController
, so you can get all the iOS native search bar features with a single line of code.NavigationView
there is a UINavigationController
instance we can start development following steps below:class SearchBarViewController : UIViewController {
let searchController: UISearchController
init( searchController:UISearchController ) {
self.searchController = searchController
super.init(nibName: nil, bundle: nil)
}
// Continue ...
}
SearchBarViewController
will be attached to UINavigationController
(behind NavigationView
) and hook up the UISearchController to the UINavigationController through the navigationItem.searchController
property.override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
guard let parent = parent,
parent.navigationItem.searchController == nil else {
return
}
parent.navigationItem.searchController = searchController
}
UIViewControllerRepresentable
named SearchBar
that create a SearchBarViewController
instance and hold a @Binding
string variable that will be used to handle the search text update events:struct SearchBar: UIViewControllerRepresentable {
typealias UIViewControllerType = SearchBarViewController
@Binding var text: String
class Coordinator: NSObject, UISearchResultsUpdating {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func updateSearchResults(for searchController: UISearchController) {
if( self.text != searchController.searchBar.text ) {
self.text = searchController.searchBar.text ?? ""
}
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<SearchBar>) -> UIViewControllerType {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = context.coordinator
return SearchBarViewController( searchController:searchController )
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<SearchBar>) {
}
}
@ViewBuilder
the custom parameter attribute that constructs views from closures, however we proceed with order following the steps below:SearchBarViewController
enabling it to manage SwiftUI View as a new attribute.class SearchBarViewController<Content:View> : UIViewController {
let searchController: UISearchController
let contentViewController: UIHostingController<Content>
init( searchController:UISearchController, withContent content:Content ) {
self.contentViewController = UIHostingController( rootView: content )
self.searchController = searchController
super.init(nibName: nil, bundle: nil)
}
// Continue
}
UIView
we have to use UIHostingController
that is able to create a UIViewController
from a SwiftUI View then we can got a UIView and adding it as sub view to the SearchBarViewController
views hierarchy.override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contentViewController.view)
contentViewController.view.frame = view.bounds
}
SearchBar
, our UIViewControllerRepresentable
, a new attribute qualified as @ViewBuilder
that hold the closure producing the contained SwiftUI View and evaluate such closure to initialize SearchBarViewController
struct SearchBar<Content: View>: UIViewControllerRepresentable {
typealias UIViewControllerType = SearchBarViewController<Content>
@Binding var text: String
@ViewBuilder var content: () -> Content // closure that produce SwiftUI content
class Coordinator: NSObject, UISearchResultsUpdating { ... }
func makeCoordinator() -> SearchBar.Coordinator { ... }
func makeUIViewController(context: UIViewControllerRepresentableContext<SearchBar>) -> UIViewControllerType {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = context.coordinator
return SearchBarViewController( searchController:searchController, withContent: content() )
}
// Continue
}
SearchBar
we have ignored implementation of updateUIViewController
now, since we are managing the SwiftUI content, it is not possible anymore. The updateUIViewController
is automatically called by SwiftUI when an update is required and as consequence we have to re-evaluate content closure passing it to the SearchBarViewController
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<SearchBar>) {
let contentViewController = uiViewController.contentViewController // get reference to UIHostingController
contentViewController.view.removeFromSuperview() // remove previous content
contentViewController.rootView = content() // assign fresh content to UIHostingController
uiViewController.view.addSubview(contentViewController.view) // add produced UIView
contentViewController.view.frame = uiViewController.view.bounds // update view geometry
}
struct ContentView: View {
@State var searchText = ""
var body: some View {
NavigationView {
SearchBar(text: $searchText ) {
List {
ForEach( (1...50)
.map( { "Item\($0)" } )
.filter({ $0.starts(with: searchText)}), id: \.self) { item in
Text("\(item)")
}
}
}.navigationTitle("Search Bar Test")
}
}
}