SwiftUI - Search in List Header

Daniel picture Daniel · Jun 15, 2019 · Viewed 10.2k times · Source

I am trying to to recreate what everyone know from UITableView with SwiftUI: A simple search field in the header of the tableview:

A simple search field in the header of the tableview

However, the List View in SwiftUI does not even seem to have a way to add a header or footer. You can set a header with a TextField to sections like this:

@State private var searchQuery: String = ""

var body: some View {

List {
    Section(header:
        Group{
            TextField($searchQuery, placeholder: Text("Search"))
                                .background(Color.white)
            }) {
                  ListCell()
                  ListCell()
                  ListCell()
                }

    }
}

However, I am not sure if this is the best way to do it because:

  1. The header does not hide when you scroll down as you know it from UITableView.
  2. The SearchField does not look like the search field we know and love.

Has anyone found a good approach? I don't want to fall back on UITableView.

Answer

Matteo Pacini picture Matteo Pacini · Jun 15, 2019

You can port UISearchBar to SwiftUI.

(More about this can be found in the excellent WWDC 2019 talk - Integrating SwiftUI)

struct SearchBar: 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
        }
    }

    func makeCoordinator() -> 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
    }
}

And use it like this:

struct ContentView: View {

    @State private var searchQuery: String = ""

    var body: some View {

        List {
            Section(header: SearchBar(text: self.$searchQuery)) {
                ForEach(Array(1...100).filter {
                    self.searchQuery.isEmpty ?
                        true :
                        "\($0)".contains(self.searchQuery)
                }, id: \.self) { item in
                    Text("\(item)")
                }
            }
        }
    }
}

It's the proper search bar, but it doesn't hide - I'm sure we'll be able to do it at some point via SwiftUI API.

Looks like this:

enter image description here

Updated for Xcode 11 beta 5