Reputation: 111
As probably most of us I updated my iPhone to iOS 15. Sadly I have noticed that the dismissal of a view via the Binding
of the NavigationLink
is no longer working as it was on iOS 14. It works with the first link but not with the second or later.
iOS 14.5:
iOS 15.0:
I created a small project that recreates the issue:
import SwiftUI
struct ContentView: View {
enum NextView: Hashable {
case view1
}
@State private var nextView: NextView?
var body: some View {
NavigationView {
VStack {
Button("Next", action: { nextView = .view1 })
}
.background(Color.red)
.background(
NavigationLink(
tag: .view1,
selection: $nextView,
destination: {
View1 { dismissReason in
nextView = nil
}
},
label: EmptyView.init
)
)
}
}
}
struct View1: View {
enum NextView: Hashable {
case view2
}
enum DismissReason {
case back
}
let onDismiss: (DismissReason) -> Void
@State private var nextView: NextView?
var body: some View {
VStack {
Button("Back", action: { onDismiss(.back) })
Button("Next", action: { nextView = .view2 })
}
.background(Color.yellow)
.background(
NavigationLink(
tag: .view2,
selection: $nextView,
destination: {
View2 { dismissReason in
nextView = nil
}
},
label: EmptyView.init
)
)
}
}
struct View2: View {
enum NextView: Hashable {
case text
}
enum DismissReason {
case back
}
let onDismiss: (DismissReason) -> Void
@State private var nextView: NextView?
var body: some View {
VStack {
Button("Back", action: { onDismiss(.back) })
Button("Next", action: { nextView = .text })
}
.background(Color.green)
.background(
NavigationLink(
tag: .text,
selection: $nextView,
destination: { Text("Destination") },
label: EmptyView.init
)
)
}
}
Upvotes: 4
Views: 1035
Reputation: 19116
I could reproduce your error and frankly, there seems to be something wrong which is not in your code. Below is a screen shot which replaces 1000 words of explanation.
Here, we are on a breakpoint after the "back" button in View2 has been tapped. The nextView
state variable has been set correctly to nil
.
When we proceed with running, the View2 does not disappear, though.
Basically, what's wrong (from my understanding) is, that the condition for rendering the destination is FALSE (which is correct), but nonetheless, View2 will be drawn - respectively not removed.
Note, that the body of View2 runs, even when the condition is false.
Why this is the case, and whether this is "legal" or a bug in SwiftUI needs to be investigated elsewhere. I would suggest to create a bug report and file it to Apple.
Update:
I found a workaround and a possible cause of this behaviour. The NavigationLink will be handled differently for iPad and iPhone. If the navigationViewStyle is column
, on iPad we always see the first view and the second view on the stack. We cannot pop "back" the second view and there is also no "back" button. The third view and any subsequent view pushed on the stack have a "back" button and can be popped of course.
The behaviour in the given code seems like as if it is using a "column" view.
So, the workaround to fix this, is for rendering on iPhone I would suggest to explicitly set stack
as the navigationViewStyle:
NavigationView {
...
}
.navigationViewStyle(.stack)
Otherwise, for iPad, it depends how you want to show the navigation stack view.
So, the default NavigationLinkStyle, which is automatic
does show this issue when running on iPhone. I would consider this a bug and report to Apple is justified.
The style columns
(which is available only in iOS 15) is not applicable for the iPhone.
Upvotes: 4