How to Integrate Uisearchcontroller with Swiftui

UISearchController how to activate from SwiftUI

To activate a UISearchBar (which is what you're using), just do:

searchController.searchBar.becomeFirstResponder()

(from this answer)

Now all we need to do is reference searchController.searchBar from the SwiftUI view. First, add a function to your SearchBar class.

class SearchBar: NSObject, ObservableObject {

@Published var text: String = ""
let searchController: UISearchController = UISearchController(searchResultsController: nil)

override init() {
super.init()
self.searchController.obscuresBackgroundDuringPresentation = false
self.searchController.searchResultsUpdater = self
}

/// add this function
func activate() {
searchController.searchBar.becomeFirstResponder()
}
}

Then, just call it. I think this is better than setting a @State, but if you require that, let me know and I'll edit my answer.

struct ContentView: View {
@StateObject var searchBar = SearchBar()

var body: some View {
NavigationView {
Button(action: {
searchBar.activate() /// activate the search bar
}) {
Text("Activate search bar")
}
.modifier(SearchBarModifier(searchBar: searchBar))
.navigationTitle("Navigation View")
}
}
}

Result:

Press custom button to activate search bar

How to incorporate a UISearchController into SwiftUI NavigationView by using UINavigationController's searchController property?

I was able to get this working without using UINavigationController in a UIViewControllerRepresentable. In fact, in my own experiments in answering this question I found that to be a quite error prone method when views get updated.

The technique here is similar to that question: Use a dummy UIViewController in order to configure your navigation.


struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Bye")) {
Text("Hi")
.background(SearchControllerSetup())
.navigationBarTitle("Hello", displayMode: .inline)
}
}
}
}

struct SearchControllerSetup: UIViewControllerRepresentable {
typealias UIViewControllerType = UIViewController

func makeCoordinator() -> SearchCoordinator {
return SearchCoordinator()
}

func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
// This will be called multiple times, including during the push of a new view controller
if let vc = uiViewController.parent {
vc.navigationItem.searchController = context.coordinator.search
}
}

}

class SearchCoordinator: NSObject {

let updater = SearchResultsUpdater()
lazy var search: UISearchController = {
let search = UISearchController(searchResultsController: nil)
search.searchResultsUpdater = self.updater
search.obscuresBackgroundDuringPresentation = false
search.searchBar.placeholder = "Type something"
return search
}()
}

class SearchResultsUpdater: NSObject, UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else { return }
print(text)
}
}

Results:
Search Bar is present

How we can adding a search bar with side bar icon to the navigation view?

As I mentioned in comments this is not possible in SwiftUI (2.0) yet. What you can do is integrating with UIKit.



Integrate with UIKit

class UIKitSearchBar: NSObject, ObservableObject {
@Published var text: String = ""
let searchController = UISearchController(searchResultsController: nil)
override init() {
super.init()
self.searchController.obscuresBackgroundDuringPresentation = false
self.searchController.definesPresentationContext = true
self.searchController.searchResultsUpdater = self
}
}

extension UIKitSearchBar: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
// Publish search bar text changes.
if let searchBarText = searchController.searchBar.text {
self.text = searchBarText
}
}
}

struct SearchBarModifier: ViewModifier {
let searchBar: UIKitSearchBar
func body(content: Content) -> some View {
content
.overlay(
ViewControllerResolver { viewController in
viewController.navigationItem.searchController = self.searchBar.searchController
}
.frame(width: 0, height: 0)
)
}
}

extension View {
func add(_ searchBar: UIKitSearchBar) -> some View {
return self.modifier(SearchBarModifier(searchBar: searchBar))
}
}

final class ViewControllerResolver: UIViewControllerRepresentable {
let onResolve: (UIViewController) -> Void
init(onResolve: @escaping (UIViewController) -> Void) {
self.onResolve = onResolve
}
func makeUIViewController(context: Context) -> ParentResolverViewController {
ParentResolverViewController(onResolve: onResolve)
}
func updateUIViewController(_ uiViewController: ParentResolverViewController, context: Context) { }
}

class ParentResolverViewController: UIViewController {
let onResolve: (UIViewController) -> Void
init(onResolve: @escaping (UIViewController) -> Void) {
self.onResolve = onResolve
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if let parent = parent {
onResolve(parent)
}
}
}


Usage

struct Example: View {

@StateObject var searchBar = UIKitSearchBar()

var body: some View {
NavigationView {
Text("Example")
.add(searchBar)
.navigationTitle("Example")
}
}
}

In my own project I am using computed property to filter stuff, it can be helpful for you too. Here is my code:

var filteredExams: [Exam] {
examModel.exams.filter({ searchBar.text.isEmpty || $0.examName.localizedStandardContains(searchBar.text)})
}


Screenshot

Sample Image



Related Topics



Leave a reply



Submit