There is an issue with SwiftUI where it is incorrectly instantiating the navigation destination view with a new identity for a navigation stack that is being removed.
Here is a minimal reproducible example:
struct ContentView: View {
@State private var signedIn = false
var body: some View {
if signedIn {
navigationStack(isSignedIn: true)
} else {
navigationStack(isSignedIn: false)
}
}
private func navigationStack(isSignedIn: Bool) -> some View {
NavigationStack(path: .constant(NavigationPath([1]))) {
EmptyView()
.navigationDestination(for: Int.self) { _ in
VStack {
Text(isSignedIn ? "Signed In" : "Signed Out")
.foregroundStyle(Color.red)
Button(isSignedIn ? "Sign Out" : "Sign In") {
signedIn = !isSignedIn
}
}
.onFirstAppear {
print(isSignedIn ? "signed in" : "signed out")
}
}
}
}
}
struct OnFirstAppearView: ViewModifier {
@State private var hasAppeared = false
var onAppear: () -> Void
func body(content: Content) -> some View {
content
.onAppear {
if hasAppeared { return }
hasAppeared = true
onAppear()
}
}
}
extension View {
func onFirstAppear(_ onAppear: @escaping () -> Void) -> some View {
ModifiedContent(content: self, modifier: OnFirstAppearView(onAppear: onAppear))
}
}
When you launch the app it will print "signed out", but when you tap to Sign In it will print "signed out" and "signed in". This shows that onAppear is incorrectly being called for a view that is disappearing and worse yet, it is with a new identity.
The onFirstAppear
modifier was created to help with detecting the identity change of the view.
Tested on Xcode 16.4, on simulator using iOS 18.5 and also on physical device using iOS 18.5.
Link to Feedback sent on Feedback Assistant: https://feedbackassistant.apple.com/feedback/18336684