Skip to content

Instantly share code, notes, and snippets.

@mattyoung
Last active August 8, 2021 10:32
Show Gist options
  • Save mattyoung/99c6a449a99fa671f528013d04b5b794 to your computer and use it in GitHub Desktop.
Save mattyoung/99c6a449a99fa671f528013d04b5b794 to your computer and use it in GitHub Desktop.
import SwiftUI
struct Child: View {
@Binding var binding: Int
var body: some View {
VStack {
Text("x = \(binding)")
Button("Mutate parent view .x") {
binding += 1 // this will cause a new child view push onto the nav stack
}
}
}
}
struct ContentView: View {
@State var x = 100
var body: some View {
NavigationView {
VStack {
Text("Hello, x = \(x)")
}
.navigationTitle(Text("Nav Stack Keep Pushing"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: Child(binding: $x)) {
// image interpolation here doesn't work
// Text("Child\(Image(systemName: "chevron.right"))")
// so have to use a HStack
HStack(spacing: 0) {
Text("Child")
Image(systemName: "chevron.right") }
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@zntfdr
Copy link

zntfdr commented Mar 26, 2021

The issue seems to be the toolbar owning (and not handling properly) the navigation link.

This is what I believe it's happening:

  1. after we push to the child, we tap the mutate button to change the binding value.
  2. this triggers a new ContentView body evaluation (hence also a new evaluation of the toolbar)
  3. The toolbar somehow knows that it's pushing a view, but during the new body evaluation it also forgets that it already did push the child view, so it pushes another one.

This causes the issue in your tweet.

I've tried moving the push state out of NavigationLink and into the ContentView, however the behavior is still the same, hence I think this is an issue with the toolbar/NavigationLink combo.

To fix this issue, I found that moving the NavigationLink completely out of the toolbar, and into the ContentView makes it work:

struct ContentView: View {
    @State var x = 100
    @State var showingChild = false
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, x = \(x)")
            }
            .navigationTitle(Text("Nav Stack Keep Pushing"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showingChild.toggle()
                    } label: {
                        HStack(spacing: 0) {
                            Text("Child")
                            Image(systemName: "chevron.right")
                        }
                    }
                }
            }
            .background(
                NavigationLink(destination: Child(binding: $x), isActive: $showingChild, label: EmptyView.init)
            )
        }
    }
}

From the UI perspective this is identical to the previous example, however the navigation state is properly handled.

@zntfdr
Copy link

zntfdr commented Aug 8, 2021

Fixed in iOS 15b4!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment