I'm wondering if I need to submit a bug report for this or if this is really what Apple intends for SwiftUI. Ideally I can find out I'm wrong.
I have a .searchable
List/Feature that can trigger my search function from the .onSubmit(of: .search)
modifier function. When I do this it seems that there is no way to perform the same behavior as dismissSearch
the Environment Value in this function. I have several views that are interchangeable inside this List's container that are all listening for my ViewModels isSearchingActive
published property. I set my ViewModel.isSearchingActive
by listening to changes of the child view's Environment Value of isSearching
. I also listen for changes to ViewModel.isSearchingActive
in my children to call the Environment Value dismissSearch
. This does not seem to work.
Here is a playground Minimal Reproducible Example. I am hoping I do not need to make a bug report, I hope I am just wrong... Thanks in advance!
import SwiftUI
import PlaygroundSupport
struct SearchingExample: View {
@State var searchText = ""
@State var didSubmit = false
var body: some View {
NavigationStack {
SearchedView(didSubmit: $didSubmit)
.searchable(text: $searchText)
.onSubmit(of: .search) {
didSubmit = true
}
}
}
}
struct SearchedView: View {
@Environment(\.isSearching) var isSearching
@Environment(\.dismissSearch) var dismissSearch
@Binding var didSubmit: Bool
var body: some View {
VStack {
Text(isSearching ? "Searching!" : "Not searching.")
Button(action: { dismissSearch() }, label: {
Text("Dismiss Search")
})
Button(action: {
// Return Key for playground
}, label: {
Image(systemName: "paperplane")
})
.frame(width: 30, height: 30)
.keyboardShortcut(.defaultAction)
if didSubmit {
Text("You Submitted Search!")
.onAppear {
Task { @MainActor in
try await Task.sleep(nanoseconds: 3_000_000_000)
self.didSubmit = false
}
}
}
}
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.setLiveView(SearchingExample())
@Polyphonic Solved this for me but the example they posted did not work for me in the xcode playground I had to prove it.
In order to call dismissSearch
from a parent view's .onSubmit(of: .search)
one must find a way to store the reference to the child view's dismissSearch
environment value in a way that the parent can call it. For example, use a ViewModel of some kind to store a function that you can overwrite from the child with the child's dismissSearch
environment value when it becomes available in an onAppear or something similar.
class ViewModel {
var dismissClosure: () -> Void = { print("Not Set") }
}
Pass this ViewModel to the child and then store the dismissSearch
in the child so the parent can call it from the shared ViewModel
.onAppear {
viewModel.dismissClosure = { dismissSearch() }
}
Here is the complete solution to the posted problem.
import SwiftUI
import PlaygroundSupport
class ViewModel {
var dismissClosure: () -> Void = { print("Not Set") }
}
struct SearchingExample: View {
@State var searchText = ""
@State var didSubmit = false
@Environment(\.dismissSearch) var dismissSearch
let viewModel = ViewModel()
var body: some View {
NavigationStack {
SearchedView(didSubmit: $didSubmit, viewModel: viewModel)
.searchable(text: $searchText)
.onSubmit(of: .search) {
didSubmit = true
viewModel.dismissClosure()
}
}
}
}
struct SearchedView: View {
@Environment(\.isSearching) var isSearching
@Environment(\.dismissSearch) var dismissSearch
@Binding var didSubmit: Bool
let viewModel: ViewModel
var body: some View {
VStack {
Text(isSearching ? "Searching!" : "Not searching.")
Button(action: { dismissSearch() }, label: {
Text("Dismiss Search")
})
Button(action: {
// Return Key for playground
}, label: {
Image(systemName: "paperplane")
})
.frame(width: 30, height: 30)
.keyboardShortcut(.defaultAction)
if didSubmit {
Text("You Submitted Search!")
.onAppear {
Task { @MainActor in
try await Task.sleep(nanoseconds: 3_000_000_000)
self.didSubmit = false
}
}
}
}
.onAppear {
viewModel.dismissClosure = { dismissSearch() }
}
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.setLiveView(SearchingExample())