UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: UIViewRepresentable SearchBar: how to get search text?

Forums > SwiftUI

I've copied Paul's demo code here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-modifiers-for-a-uiviewrepresentable-struct into a new project.

I've added Text(text) to the VStack just below the button, but it doesn't update when I type. How do I get the typed search string out of the SearchBar?

Jeremy

2      

It's the text variable that is passed into SearchBar as a Binding.

2      

That's what I thought - so shouldn't this work (with the rest of Paul's code at the top)?

struct ContentView: View {
    @State private var text = ""
    @State private var placeHolder = "Hello, world!"

var body: some View {
        VStack {
            SearchField(text: $text)
                .placeholder(placeHolder)

            Button("Tap me") {
                // randomize the placeholder every press, to
                // prove this works
                placeHolder = UUID().uuidString
      }

            Text("search for '\(text)'")
        }
    }
}

2      

text is not being modified in SearchField, and in the ContentView it is initialised to an empty string.

Try replacing the declaration with

@State private var text = "Some text"

You will see that the search bar now has 'Some text', and pressing the Tap me has no effect. Return that line of code back to the original.

@State private var text = ""

Try using the placeHolder parameter in your text field instead of text

Text("Search for \(placeHolder)")

This will update each time Tap me is pressed, because of the update happening for view.placeholder in the extension.

extension SearchField {
    func placeholder(_ string: String) -> SearchField {
        var view = self
        view.placeholder = string
        return view
    }
}

The UIView defines the search bar context, and uses the placeholder text if the main text is empty, which it always is in this case (until you actually enter some text into the search bsr).

func makeUIView(context: Context) -> UISearchBar {
    let searchBar = UISearchBar()
    searchBar.placeholder = placeholder
    return searchBar
}

// Always copy the placeholder text across on update
func updateUIView(_ uiView: UISearchBar, context: Context) {
    uiView.text = text
    uiView.placeholder = placeholder
}

2      

Greenamber, I'm obviously missing something essential. To be useful, it must be possible to get the typed text somehow. How can this be done, if not by using the text binding passed to SearchField?

2      

kikashi59, this is not something that I am that familiar with, however after some searching through the UIViewRepresentable Apple documentation and searching on the internet (found this which helped), I now have a better understanding.

The situation is, as I said earlier, text is not being updated. However, it is not so clear how to update it, and so how the view also gets updated.

What was needed is a coordinator, where the text is updated, and using that coordinator in a delegate to the searchBar (UISearchBar()). Without the searchBar.delegate, the text is not updated.

So there is a new class, using the makeCoordinator() function and assigning the context coordinator to the searchBar delegate.

A new class.

// Create the class to use with the searchBar delegate for the search string / text
class Cordinator : NSObject, UISearchBarDelegate {

    @Binding var text : String

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

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

The coordinator set up to use the text

// Create coordinator for the search delegate
func makeCoordinator() -> SearchField.Cordinator {
    return Cordinator(text: $text)
}

Set up the searchBar delegate.

searchBar.delegate = context.coordinator    // apply the coordinator to the seachBar

I have added a list of fruit, as an example, to use for the search.

Here is the whole main code.

import SwiftUI

struct SearchField: UIViewRepresentable {
    @Binding var text: String

    private var placeholder = ""

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

    // Create the class to use with the searchBar delegate for the search string / text
    class Cordinator : NSObject, UISearchBarDelegate {

        @Binding var text : String

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

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

    // Create coordinator for the search delegate
    func makeCoordinator() -> SearchField.Cordinator {
        return Cordinator(text: $text)
    }

    func makeUIView(context: Context) -> UISearchBar {
        let searchBar = UISearchBar()
        searchBar.delegate = context.coordinator    // apply the coordinator to the searchBar
        searchBar.placeholder = placeholder
        return searchBar
    }

    // Always copy the placeholder text across on update
    func updateUIView(_ uiView: UISearchBar, context: Context) {
        uiView.text = text
        uiView.placeholder = placeholder
    }
}

// Any modifiers to adjust your search field – copy self, adjust, then return.
extension SearchField {

    func placeholder(_ string: String) -> SearchField {
        var view = self
        view.placeholder = string
        return view
    }
}

struct ContentView: View {

    @State private var text = ""
    @State private var placeHolder = "Hello, world!"

    let fruitList = ["Apple", "Banana", "Pear", "Orange", "Strawberry"]

    var body: some View {

        VStack {
            NavigationView{
                List {
                    SearchField(text: $text)
                        .placeholder(placeHolder)

                    ForEach( self.fruitList.filter {
                                self.text.isEmpty ?
                                true :
                                $0.localizedStandardContains(self.text)},
                             id: \.self)
                    { fruitName in
                        Text(fruitName)
                    }
                }
                .navigationBarTitle(Text("Search bar"))
            }

            VStack {
                Button("Tap me") {
                    // randomize the placeholder every press, to
                    // prove this works
                    placeHolder = UUID().uuidString
                }
                .padding(.bottom, 10)

                Text("Placeholder is \(placeHolder)")
                    .padding(.bottom, 10)
                Text(text.isEmpty ? "Search for Nothing" : "Searching for '\(text)'")
                Spacer()
            }
        }
    }
}

2      

Wow - that is superbly helpful. Thank you very much!

Jeremy

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.