Reputation: 1037
I am trying to to recreate what everyone know from UITableView with SwiftUI: 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:
Has anyone found a good approach? I don't want to fall back on UITableView.
Upvotes: 16
Views: 14892
Reputation: 6040
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
Upvotes: 1
Reputation: 22846
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:
Upvotes: 34
Reputation: 912
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)""#
}
}
Upvotes: 5
Reputation: 10500
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
Upvotes: 2