SwiftUI NavigationLink pops out by itself

I have a simple use case where a screen pushes another screen using the NavigationLink. There is a strange behaviour iOS 14.5 beta where the pushed screen is popped just after being pushed.

I manage to create a sample app where I reproduce it. I believe the cause is the presence of @Environment(\.presentationMode) that seem to re-create the view and it causes the pushed view to be popped.


The exact same code works fine in Xcode 12 / iOS 14.4



Here is a sample code.

Code Block swift
import SwiftUI
public struct FirstScreen: View {
  public init() {}
  public var body: some View {
    NavigationView {
      List {
        row
        row
        row
      }
    }
  }
  private var row: some View {
    NavigationLink(destination: SecondScreen()) {
      Text("Row")
    }
  }
}
struct SecondScreen: View {
  @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
  public var body: some View {
    VStack(spacing: 10) {
      NavigationLink(destination: thirdScreenA) {
        Text("Link to Third Screen A")
      }
      NavigationLink(destination: thirdScreenB) {
        Text("Link to Third Screen B")
      }
      Button("Go back", action: { presentationMode.wrappedValue.dismiss() })
    }
  }
  var thirdScreenA: some View {
    Text("thirdScreenA")
  }
  var thirdScreenB: some View {
    Text("thirdScreenB")
  }
}
struct FirstScreen_Previews: PreviewProvider {
  static var previews: some View {
    FirstScreen()
  }
}






I have this same problem. Has anyone found a solution for this bug? A public release of iOS 14.5 is nearing, and I'm looking to get it figured out beforehand.

I did see in the release notes of 14.5 beta 2 the following: "Using Published in a subclass of a type conforming to ObservableObject now correctly publishes changes. (71816443)". (Not sure if this is the cause).

This was also in the release notes of iOS 14.5 beta 2 which could more likely be the cause of the bug:
"The destination of NavigationLink that only differs by local state now resets that state when switching between links as expected. (72117345)"
I haven’t find a solution nor a workaround and this behavior is quite bad. I will file a radar.
please let me know if you find something
Same problem here. iOS 14.5 messed up the whole app navigation und now views are immediately popped upon being pushed so the App jumps around and is unusable. I fear this behavior will find its way into the release version as it is also present in the latest release candidate.

Update: Found a fix which is really weird but seems to work until the real cause is found:

In the pushing view that contains the NavigationLinks, add the following meaningless Navigationlink next to your existing ones. So far it seems the problem only occurs if you have more than one NavigationLink in a view and I am sure its related to the mentioned new behavior of 14.5

Add this to your view containing your existing NavigationLinks:

Code Block swift
NavigationLink(destination: EmptyView()) {
    EmptyView()
}



33
yes, this seems to be a (super strange) workaround. There is a discussion about this on the Swift forums here


cheers
I can confirm that this issue is still present in the release version of iOS 14.5.
Workaround is also working here.
Thank you so much, the empty navigation link works for me as well. The behaviour is still present in the current iOS 14.6 beta.
We updated our whole app and placed the empty NavigationLink in every view which already had a NavigationLink and it solved the problem for us :) Sad thing is that with the 14.5 being released, we will have to keep that thing in place for ages now even if 14.6 may solve it :/
Same problem here, fixed it with the empty NavigationLink hack, but I hope this will be fixed soon...
We've also faced this issue. Trying to get attention from Apple Engineers
Having the same issue, thanks for the workaround! Have filed a bug report, make sure you all file one too. You can reference case #FB9095424 and hopefully we can drum up some noise so the Apple Engineers notice! 🤞
Damn, thank you so much for that workaround!
I was going bananas over here after multiple users reported that bug to me today.
THANK YOU!
Thank you so much. I wasted hours on this as well.
Any updates? I've seen several threads on this and I can't figure out is it's a SwiftUI bug or not setting some state variable properly.
Spent several hours on this issue thinking I was triggering the pop myself. The workaround worked for me - thank you.
Where exactly are you putting the empty navigation link? I've tried putting inside my List, inside my ForEach, before and after the list, before and after the For Each, and it only seems to fix my first link. All the others keep popping when I tap on a button in the detail view.

The button I'm pressing in the detail view makes changes to core data. Could it be a different issue? Could it be that making changes to the model, which the master view uses as well, causes the pop?
@coopersita
For us it worked placing them at the same level as the other NavigationLinks already present in the same view.


A simple workaround is to have one overlay for the navigationLinks and activate it conditionally.
Seeing this exact issue and can confirm that the empty navigation link does seem to resolve it for me too. I do see Unable to present. Please file a bug. in the console too.

This has retroactively broken our App Store build with no updates which is the most annoying part..
For me adding the
Code Block
NavigationLink(destination: EmptyView()) {
  EmptyView()
}

still created a weird behaviour depending on the number of NavigationLinks I have.
I've ended up just adding a EmptyView() and it solved the problem.
I think this all stems from the original SwiftUI design decision that navigation link destinations are not lazy loaded. What a pain navigation has been from the beginning and getting more and more messier.
Once @basememara mentioned that is may have something to do with the lazy loading I immediately thought about some (but not all) of my NavigationLinks using the following wrapper:

Code Block Swift
struct NavigationLazyView<Content: View>: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}
NavigationLink(destination: NavigationLazyView(DetailView())) { Text("Click me") }

Adopted from this source.


So I tried wrapping every destination with the aforementioned wrapped and it seems that the navigation stopped popping views off the stack back and forth. The Unable to present. Please file a bug. is still there, but navigation seems to be working fine. Will post an update it the popping horror comes back.

EDIT: It still pops back and forth, but I think not so often as before. To be honest I already don't know if it is my fault or apple's that this works like that. In SwiftUI sometimes things just start working differently without any iOS version updates or anything like that. Feels like a live lab.
Another approach to the navigation link workaround collection is placing a hidden NavigationLink in the background that is shared across a few buttons, instead of each button being a NavigationLink:

Code Block swift
extension View {
    func navigate<Destination: View>(
        isActive: Binding<Bool>,
        destination: Destination?
    ) -> some View {
        background(
            NavigationLink(
                destination: destination,
                isActive: isActive,
                label: EmptyView.init
            )
            .hidden()
        )
    }
}
extension View {
    func navigate<Item, Destination: View>(
        item: Binding<Item?>,
        destination: (Item) -> Destination
    ) -> some View {
        navigate(
            isActive: Binding(
                get: { item.wrappedValue != nil },
                set: { if !$0 { item.wrappedValue = nil } }
            ),
            destination: Group {
                if let item = item.wrappedValue {
                    destination(item)
                }
            }
        )
    }
}

Then you can use it like this:

Code Block swift
struct ShowMoreView: View {
    @State private var isLinkActive = false
    var body: some View {
        List {
            Button(action: { isLinkActive = true })
                Text("Start navigation 1")
            }
            Button(action: { isLinkActive = true })
                Text("Start navigation 2")
            }
            Button(action: { isLinkActive = true })
                Text("Start navigation 3")
            }
        }
        .navigate(isActive: $isLinkActive, destination: makeDestination())
    }
}
struct ShowMoreView: View {
    @State private var date: Date?
    var body: some View {
        List {
            Button(action: { date = Date() })
                Text("Start navigation 1")
            }
            Button(action: { date = Date() + 100 })
                Text("Start navigation 2")
            }
            Button(action: { date = Date() + 1000 })
                Text("Start navigation 3")
            }
        }
        .navigate(item: $date, destination: makeDestination)
    }
    func makeDestination(for date: Date) -> some View {
        ...
    }
}


This source is from SwiftWithMajid's blog (for some reason the forums is not allowing me to link to it, but search for "swiftui majid lazy navigation" and you'll find it).
I'm having similar issues but in macOS! Updating to macOS 11.3 w/ Xcode 12.5 messed up my existing app which worked just fine in previous versions. How is it that Apple hasn't fixed this.

update: tried the EmptyView() and it doesn't help. Clicking the navigation link still leads to a "unable to present, please file a bug" error.

Frustrating.
Same issue here. Alas the workaround doesn't work in my case.

I am using a @State variable to store the isActive state and I had noticed it being false after I had set it to true during some renders of the body property. It renders the body with it true then all of a sudden it is false. Its almost like SwiftUI created a totally new View.

I tried putting the isActive state in my view model (I have an MVVM app) but I was able to watch it getting set to false through no action on my own (I guess when SwiftUI popped the newly pushed on view.) I also noticed my viewModel (passed via @EnviromentObject) was also nil during some of the body property calls.
I was able to resolve all navigation issues and state side effects in my app (iOS & iPadOS) using an adaptation of the method referenced by "basememara" and originating from "Swift with Majid". I'm mostly using grid and list based item views with navigation links.

SwiftUI is broken in so many areas currently, that the slowdown you get by finding many plan B's will eat all its proposed swiftness. I filed bug reports for all of them, please do the same.
SwiftUI NavigationLink pops out by itself
 
 
Q