When tapping a NavigationLink within a NavigationStack, my app freezes and is unrecoverable:
import SwiftUI
@main
struct State_BugApp: App {
var body: some Scene {
WindowGroup {
NavigationSplitView {
List {
Section {
Text("Home")
}
}.listStyle(.insetGrouped)
} detail: {
NavigationStack {
MainView()
}
}
}
}
}
struct MainView: View {
@Environment(\.dismiss) private var dismiss
@State var action: String = "Open child"
var body: some View {
List {
NavigationLink {
listDestination()
} label: {
Text(action)
}
}
}
@ViewBuilder func listDestination() -> some View {
ChildView(action: { value in foo() })
}
func foo() {
self.action = "Updated action"
}
}
struct ChildView: View {
@StateObject var viewModel: ChildViewModel = ChildViewModel()
var action: (String) -> Void
init(action: @escaping (String) -> Void) {
self.action = action
}
var body: some View {
List {
Text("Text")
}
}
}
class ChildViewModel: ObservableObject {
var title: String = "Child"
}
If you remove the @Environment(\.dismiss)
it works, however I then can’t dismiss my view at all….
The issue stems from the fact that the dismiss value is not equatable and is therefore causing a cycle, because the view redraws endlessly when tapping the NavigationLink
. It’s documented here https://hachyderm.io/@teissler/112533860374716961 and here https://forums.developer.apple.com/forums/thread/720096#:~:text=This%20is%20a%20problem%20if,to%20construct%20the%20presented%20view. but I can’t figure out how you’d resolve the requirement to use dismiss
.
This seems to be a known bug with SwiftUI, present even in iOS version 17.
To get around it, you can dismiss your views with presentationMode
, like this
struct DismissThisView: View {
@Environment(\.presentationMode) var mode
var body: some View {
Button("Dismiss!") {
mode.wrappedValue.dismiss()
}
}
}
This avoids having to reference the \.dismiss
environment variable, which avoids the hanging behavior when trying to use NavigationLinks.