I want to allow the user to filter data in a long list to more easily find matching titles.
I have placed a TextView inside my navigation bar:
.navigationBarTitle(Text("Library"))
.navigationBarItems(trailing: TextField("search", text: $modelData.searchString)
I have an observable object which responds to changes in the search string:
class DataModel: ObservableObject {
@Published var modelData: [PDFSummary]
@Published var searchString = "" {
didSet {
if searchString == "" {
modelData = Realm.studyHallRealm.objects(PDFSummary.self).sorted(by: { $0.name < $1.name })
} else {
modelData = Realm.studyHallRealm.objects(PDFSummary.self).sorted(by: { $0.name < $1.name }).filter({ $0.name.lowercased().contains(searchString.lowercased()) })
}
}
}
Everything works fine, except I have to tap on the field after entering each letter. For some reason the focus is taken away from the field after each letter is entered (unless I tap on a suggested autocorrect - the whole string is correctly added to the string at once)
Before NavigationStack and NavigationSplitView , SwiftUI introduced the NavigationView and NavigationLink structs. These two pieces work together to allow navigation between views in your application. The NavigationView is used to wrap the content of your views, setting them up for subsequent navigation.
Getting a basic NavigationView with a title struct ContentView: View { var body: some View { NavigationView { Text("Hello, World!") } } } For simpler layouts navigation views should be the top-level thing in your view, but if you're using them inside a TabView then the navigation view should be inside the tab view.
The problem is in rebuilt NavigationView
completely that result in dropped text field focus.
Here is working approach. Tested with Xcode 11.4 / iOS 13.4
The idea is to avoid rebuild NavigationView
based on knowledge that SwiftUI engine updates only modified views, so using decomposition we make modifications local and transfer desired values only between subviews directly not affecting top NavigationView
, as a result the last kept stand.
class QueryModel: ObservableObject {
@Published var query: String = ""
}
struct ContentView: View {
// No QueryModel environment object here -
// implicitly passed down. !!! MUST !!!
var body: some View {
NavigationView {
ResultsView()
.navigationBarTitle(Text("Library"))
.navigationBarItems(trailing: SearchItem())
}
}
}
struct ResultsView: View {
@EnvironmentObject var qm: QueryModel // << injected here from top
var body: some View {
VStack {
Text("Search: \(qm.query)") // receive query string
}
}
}
struct SearchItem: View {
@EnvironmentObject var qm: QueryModel // << injected here from top
@State private var query = "" // updates only local view
var body: some View {
let text = Binding(get: { self.query }, set: {
self.query = $0; self.qm.query = $0; // transfer query string
})
return TextField("search", text: text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(QueryModel())
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With