JohnSF
JohnSF

Reputation: 4300

SwiftUI Wrapped SearchBar Can't Dismiss Keyboard

I am using a UIViewRepresentable to add a search bar to a SwiftUI project. The search bar is for searching the main List - I have setup the search logic and it works fine, however I have not been able to code the keyboard to disappear when the search is cancelled. The Cancel button does not respond. If I click the textfield clearButton the search is ended and the full list appears but the keyboard does not disappear. If I uncomment the resignFirstResponder line in textDidChange, the behavior is as expected, except that the keyboard disappears after every character.

Here's the search bar:

import Foundation
import SwiftUI

struct MySearchBar: UIViewRepresentable {
    @Binding var sText: String

    class Coordinator: NSObject, UISearchBarDelegate {
        @Binding var sText: String

        init(sText: Binding<String>) {
            _sText = sText
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            sText = searchText
            //this works for EVERY character
            //searchBar.resignFirstResponder()
        }
    }

    func makeCoordinator() -> MySearchBar.Coordinator {
        return Coordinator(sText: $sText)
    }

    func makeUIView(context: UIViewRepresentableContext<MySearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.showsCancelButton = true
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<MySearchBar>) {
        uiView.text = sText
    }

    func searchBarCancelButtonClicked(searchBar: UISearchBar) {
        //this does not work
        searchBar.text = ""
        //none of these work

        searchBar.resignFirstResponder()
        searchBar.showsCancelButton = false
        searchBar.endEditing(true)
    }
}

And I load a MySearchBar in SwiftUI in the List.

var body: some View {
    NavigationView {
        List {
            MySearchBar(sText: $searchTerm)
            if !searchTerm.isEmpty {

                ForEach(Patient.filterForSearchPatients(searchText: searchTerm)) { patient in
                    NavigationLink(destination: EditPatient(patient: patient, photoStore: self.photoStore, myTextViews: MyTextViews())) {
                        HStack(spacing: 30) {

                        //and the rest of the application

Xcode Version 11.2 beta 2 (11B44). SwiftUI. I have tested in the simulator and on a device. Any guidance would be appreciated.

Upvotes: 4

Views: 2682

Answers (2)

jblo
jblo

Reputation: 111

The searchBarCancelButtonClicked call will be passed to the UISearchBars delegate, which is set to Coordinator, so put func searchBarCancelButtonClicked there instead.

Also, when you are clearing the text you shouldnt set searchBar.text = "", which is setting the instance of UISearchBars text property directly, but instead clear your Coordinators property text. In that way your SwiftUI View will notice the change because of the @Binding property wrapper, and know that it´s time to update.

Here is the complete code for `MySearchBar´:

import UIKit
import SwiftUI

struct MySearchBar : UIViewRepresentable {

    @Binding var text : String

    class Coordinator : NSObject, UISearchBarDelegate {

        @Binding var text : String

        init(text : Binding<String>) {
            _text = text
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
            searchBar.showsCancelButton = true
        }

        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            text = ""
            searchBar.showsCancelButton = false
            searchBar.endEditing(true)
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}

Upvotes: 2

Vlad
Vlad

Reputation: 1041

The reason searchBarCancelButtonClicked is not being called is because it is in MySearchBar but you have set the Coordinator as the search bars delegate. If you move the searchBarCancelButtonClicked func to the Coordinator, it will be called.

Here is what the coordinator should look like:

class Coordinator: NSObject, UISearchBarDelegate {
    @Binding var sText: String

    init(sText: Binding<String>) {
        _sText = sText
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        sText = searchText
    }

    func searchBarCancelButtonClicked(searchBar: UISearchBar) {

        searchBar.text = ""

        searchBar.resignFirstResponder()
        searchBar.showsCancelButton = false
        searchBar.endEditing(true)
    }
}

Upvotes: 3

Related Questions