Adding a Search Bar to a SwiftUI Picker

Recently I’ve been working on an app that will help Ceramicists like my fiancée Danika manage their glaze recipes, inventory and other complex tasks better. In the app, a user can select from a Picker containing hundreds of raw materials, and scrolling through this Picker consumed a lot of time and was a poor User Experience, as Danika quite rightly pointed out.

What I didn’t realise was how simple it is to add search functionality to the Picker. I considered creating my own, but once I started digging in, I realised there had to be a better way. That’s when I realised that the solution had been staring me in the face all along!

Creating a Search Bar in SwiftUI#

Unfortunately SwiftUI does not yet provide a Search Bar out-of-the-box. The quickest solution is to leverage UISearchBar from UIKit, and port it using UIViewRepresentable:

struct SearchBar: UIViewRepresentable {

    @Binding var text: String
    var placeholder: String

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

        searchBar.placeholder = placeholder
        searchBar.autocapitalizationType = .none
        searchBar.searchBarStyle = .minimal
        return searchBar
    }

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

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

    class Coordinator: NSObject, UISearchBarDelegate {

        @Binding var text: String

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

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

Adding a Search Bar to a Picker#

Now that we have our Search Bar available to us in SwiftUI, we can make use of it. In my example below, I create my Picker inside a Form as normal, with an array of strings to choose from. From that point, I only need to add a few more lines.

To start with, I add a @State searchTerm variable to hold my search term, and the SearchBar above the existing ForEach. This code will compile nicely and displays my SearchBar above the Picker list, but the search functionality doesn’t yet work.

To get it working, I add a computed property filteredCountries, which filters my countries array based on the searchTerm provided. If searchTerm is an empty string, we don’t apply a filter and return all the countries to be displayed.

struct FormView: View {

    let countries = ["Brazil", "Canada", "Egypt", "France", "Germany", "United Kingdom"]

    @State private var pickerSelection: String = ""
    @State private var searchTerm: String = ""
    
    var filteredCountries: [String] {
        countries.filter {
            searchTerm.isEmpty ? true : $0.lowercased().contains(searchTerm.lowercased())
        }
    }

    var body: some View {
        NavigationView {
            Form {
                Picker(selection: $pickerSelection, label: Text("")) {
                    SearchBar(text: $searchTerm, placeholder: "Search Countries")
                    ForEach(filteredCountries, id: \.self) { country in
                        Text(country).tag(country)
                    }
                }
            }
        }
    }
}

Running the code again at this point will give you everything you need to search your Picker lists!

Conclusion#

And that’s it! I honestly didn’t realise that this was possible. I had searched online previously, but couldn’t find anything that demonstrated how straightforward it was.

If this tutorial helped you in any way, I’d really appreciate a share on Twitter. You can reach out to me via Twitter if you have any questions, or to show me what you’ve been up to!

You can also check our Danika’s ceramic work here.

The completed solution can be found here on GitHub.