SwiftUI - Search in List Header

I implemented my country picker project Columbus using SwiftUI. I implemented a custom publisher CountryListViewModel and connected that with the text field. This way I can type, search the data source, filter out results / debounce and update the table view when all operations are done. Works pretty well 👍

https://github.com/Blackjacx/Columbus/tree/swift-ui/Source/Classes


Xcode 13 / SwiftUI 3

You can now use .searchable to make a List... searchable!

struct ContentView: View {

    @State private var searchQuery: String = ""

    var body: some View {
        NavigationView {
            List {
                ForEach(Array(1...100)
                            .map { "\($0)" }
                            .filter { searchQuery.isEmpty ? true : $0.contains(searchQuery) }
                        ,id: \.self) { item in
                    Text(verbatim: item)
                }
            }
            .navigationTitle("Fancy Numbers")
            .searchable(text: $searchQuery)
        }
    }

}

The search bar seems to appear only if the List is embedded in a NavigationView.


Xcode 12, SwiftUI 1/2

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


Perhaps a starting point here. Consider using ZStack for disappear effect on scroll for scrollview.


struct ContentView: View {
    @State var search: String

    var body: some View {
        NavigationView {
            VStack(alignment: .center, spacing: 0) {
                HStack {
                    Image(systemName: "magnifyingglass")
                        .padding(.leading, CGFloat(10.0))
                    TextField("Search", text: $search, onEditingChanged: { active in
                        print("Editing changed: \(active)")
                    }, onCommit: {
                        print("Commited: \(self.search)")
                    })
                        .padding(.vertical, CGFloat(4.0))
                        .padding(.trailing, CGFloat(10.0))
                }
                    .overlay(
                        RoundedRectangle(cornerRadius: 5.0)
                            .stroke(Color.secondary, lineWidth: 1.0)
                    )
                    .padding()
                List {
                    ForEach(0...100, id: \.self) { e in
                        Text("Item \(e)")
                    }
                }
            }
            .navigationBarTitle(title(for: self.search))
        }
    }

    private func title(for value: String?) -> String {
        guard let value = value, value.count > 0 else {
            return "No search"
        }

        return #"Searching for "\#(value)""#
    }
}

Screenshot


2021 — Xcode 13 / SwiftUI 3

Native SwiftUI solution:

List {
    ForEach(0..<5) {
        index in
            Text("item \(index)")
        }
    }
}
.searchable(text: .constant("search_value")) // should be a Binding