Wow. You have stumbled on some really weird behavior:
The @Environment(\.dismissSearch)
property doesn't get populated with a useful value if it's in the same View
that applies the .searchable
modifier. My guess is searchable
is responsible for putting the DismissSearchAction
in the environment.
The onSubmit
modifier doesn't work if it's applied inside (before) the searchable
modifier. My guess is onSubmit
stores the callback in the environment for searchable
to read.
This leads to needing a convoluted view structure where you apply onSubmit
outside (after) the searchable
modifier and have it set an @State
property which you pass down as a Binding
and read in an onChange
modifier on your inner view, where you have the @Environment(\.dismissSearch)
property available.
I've wrapped it all up into a handy modifier, searchableOnce
, which is like searchable
but dismisses on submit. Here's how your code looks with this modifier:
struct GroceryListView2: View {
@State private var searchQuery = ""
var body: some View {
NavigationView {
Text("Hi")
.navigationBarItems(
trailing:
Button(action: {}, label: { Image(systemName: "gear")})
)
.searchableOnce(
text: $searchQuery,
placement: .navigationBarDrawer(displayMode: .automatic),
prompt: "Add or search"
)
}
}
}
And here's the modifier implementation:
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
extension SwiftUI.View {
public func searchableOnce(text: SwiftUI.Binding<Swift.String>, placement: SwiftUI.SearchFieldPlacement = .automatic, prompt: SwiftUI.Text? = nil) -> some SwiftUI.View {
return SearchableOnce(content: self, text: text, placement: placement, prompt: prompt)
}
public func searchableOnce(text: SwiftUI.Binding<Swift.String>, placement: SwiftUI.SearchFieldPlacement = .automatic, prompt: SwiftUI.LocalizedStringKey) -> some SwiftUI.View {
return SearchableOnce(content: self, text: text, placement: placement, prompt: Text(prompt))
}
@_disfavoredOverload public func searchableOnce<S>(text: SwiftUI.Binding<Swift.String>, placement: SwiftUI.SearchFieldPlacement = .automatic, prompt: S) -> some SwiftUI.View where S : Swift.StringProtocol {
return SearchableOnce(content: self, text: text, placement: placement, prompt: Text(prompt))
}
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
fileprivate struct SearchableOnce<Content: View>: View {
@State private var wantDismiss = false
var content: Content
var text: Binding<String>
var placement: SearchFieldPlacement
var prompt: Optional<Text>
var body: some View {
Dismisser(wantDismiss: $wantDismiss, content: content)
.searchable(text: text, placement: placement, prompt: prompt)
.onSubmit(of: .search) {
wantDismiss = true
}
}
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
fileprivate struct Dismisser<Content: View>: View {
@Environment(\.dismissSearch) private var dismissSearch
@Binding var wantDismiss: Bool
var content: Content
var body: some View {
content
.onChange(of: wantDismiss) { newValue in
if newValue {
dismissSearch()
wantDismiss = false
}
}
}
}