'Using .searchable on a macOS causes the focus to always jump back to the search field
I'm trying to move away from having a TextField
in the toolbar by using the new .searchable
. But there seems to be a problem I can't solve. When you type the text you want to search, I can filter the list with that text, but when I place the mouse cursor on the first item and try to move down the list with the arrow key, with each arrow key press, the focus goes back to the search field, making it impossible to navigate up and down the list with the keyboard.
Maybe I'm not implementing it right, or maybe it doesn't work yet with macOS, either way, this is the code I'm using:
struct AllNotes: View {
@EnvironmentObject private var data: DataModel
@State var selectedNoteId: UUID?
@State var searchText: String = ""
var body: some View {
NavigationView {
List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { note in
NavigationLink(
destination: NoteView(note: note, text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(getFirstLine(noteText: note.text)).font(.body).fontWeight(.bold)
}
}
}
.searchable(
text: $searchText,
placement: .toolbar,
prompt: "Search..."
)
.listStyle(InsetListStyle())
.toolbar {
// a few other buttons
}
}
}
}
The DataModel is simple a struct of NoteItem:
struct NoteItem: Codable, Hashable, Identifiable {
let id: UUID
var text: String
var changed: Bool = false
}
Am I missing anything? Am I implementing this right?
EDIT:
Based on suggestions from Apple and other sites, .searchable
should be added under the navigation view. So I moved that there. The default behavior, as described by Apple, of adding it to the end of the toolbar is still happening, but that's ok. However the problem still persists, the focus jumps back to the search field each time you click on a list item.
struct AllNotes: View {
@EnvironmentObject private var data: DataModel
@State var selectedNoteId: UUID?
@State var searchText: String = ""
var body: some View {
NavigationView {
List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { note in
NavigationLink(
destination: NoteView(note: note, text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(getFirstLine(noteText: note.text)).font(.body).fontWeight(.bold)
}
}
}
.listStyle(InsetListStyle())
.toolbar {
// a few other buttons
}
}
.searchable(
text: $searchText,
placement: .toolbar,
prompt: "Search..."
)
}
}
Solution 1:[1]
I think the problem is because you are showing the list in the sidebar but have the search field in the toolbar. So you could try moving the search field to the sidebar which does fix the problem with navigating items with arrow keys but I wasn't able to tab back to the search field. And InsetListStyle
didn't seem compatible with searching so I commented that. And by the way, you are missing the default detail view for your NavigationView
so you need to add that. Also your View
structure needed tweaked so you pass the filtered results into the child View
E.g.
struct NoteView: View {
let note: NoteItem
//let text: String
var body: some View {
Text(note.text)
}
}
struct NotesView: View {
@State private var selectedNoteId: UUID?
let notes: [NoteItem]
var body: some View {
List(notes) { note in
NavigationLink(
destination: NoteView(note: note), //text: note.text),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text).font(.body).fontWeight(.bold)
}
}
}
// .listStyle(InsetListStyle())
}
}
struct SearchView: View {
@EnvironmentObject private var data: DataModel
@State var searchText: String = ""
var body: some View {
NavigationView {
NotesView(notes: filteredNotes)
Text("Make a selection")
// .toolbar {
// // a few other buttons
// }
}
.searchable(
text: $searchText,
placement: .sidebar,
prompt: "Search..."
)
}
var filteredNotes: [NoteItem] {
data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText)
}
}
}
struct ContentView: View {
@StateObject var model = DataModel()
var body: some View {
SearchView()
.environmentObject(model)
}
}
class DataModel: ObservableObject {
@Published var notes: [NoteItem] = [NoteItem(text: "Test1"), NoteItem(text: "Test2")]
}
struct NoteItem: Codable, Hashable, Identifiable {
let id = UUID()
var text: String
var changed: Bool = false
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | malhal |