Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my SwiftUI app crash when navigating backwards after placing a `NavigationLink` inside of a `navigationBarItems` in a `NavigationView`?

EDIT: This has been fixed in iOS 13.3!

Minimal reproducible example (Xcode 11.2 beta, this works in Xcode 11.1):

struct Parent: View {     var body: some View {         NavigationView {             Text("Hello World")                 .navigationBarItems(                     trailing: NavigationLink(destination: Child(), label: { Text("Next") })                 )         }     } }  struct Child: View {     @Environment(\.presentationMode) var presentation     var body: some View {         Text("Hello, World!")             .navigationBarItems(                 leading: Button(                     action: {                         self.presentation.wrappedValue.dismiss()                     },                     label: { Text("Back") }                 )             )     } }  struct ContentView: View {     var body: some View {         Parent()     } } 

The issue seems to lie in placing my NavigationLink inside of a navigationBarItems modifier that's nested inside of a SwiftUI view whose root view is a NavigationView. The crash report indicates that I'm trying to pop to a view controller that doesn't exist when I navigate forward to Child and then back to Parent.

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Tried to pop to a view controller that doesn't exist.' *** First throw call stack: 

If I were to instead place that NavigationLink in the body of the view like the below, it works just fine.

struct Parent: View {     var body: some View {         NavigationView {             NavigationLink(destination: Child(), label: { Text("Next") })         }     } } 

Is this a SwiftUI bug or expected behavior?

EDIT: I've opened an issue with Apple in their feedback assistant with the ID FB7423964 in case anyone out there from Apple cares to weigh in :).

EDIT: My open ticket in the feedback assistant indicates there are 10+ similar reported issues. They've updated the resolution with Resolution: Potential fix identified - For a future OS update. Fingers crossed that the fix lands soon.

like image 790
Robert Avatar asked Oct 16 '19 01:10

Robert


People also ask

How does NavigationLink work SwiftUI?

SwiftUI's NavigationLink has a second initializer that has an isActive parameter, allowing us to read or write whether the navigation link is currently active. In practical terms, this means we can programmatically trigger the activation of a navigation link by setting whatever state it's watching to true.

What is navigation view SwiftUI?

A view for presenting a stack of views that represents a visible path in a navigation hierarchy. iOS 13.0–16.0 Deprecated iPadOS 13.0–16.0 Deprecated macOS 10.15–13.0 Deprecated Mac Catalyst 13.0–16.0 Deprecated tvOS 13.0–16.0 Deprecated watchOS 7.0–9.0 Deprecated.


1 Answers

This was quite a pain point for me! I left it until most of my app was completed and I had the mind space to deal with the crashing.

I think we can all agree that there's some pretty awesome stuff with SwifUI but that the debugging can be difficult.

In my opinion, I would say that this is a BUG. Here is my rationale:

  • If you wrap the presentationMode dismiss call in an asynchronous delay of about a half-second, you should find that the program will no longer crash.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {     self.presentationMode.wrappedValue.dismiss() }  
  • This suggests to me that the bug is an unexpected behaviour way down deep in how SwiftUI interfaces with all the other UIKit code to manage the various views. Depending on your actual code, you might find that if there is some minor complexity in the view, the crash actually will not happen. For example, if you are dismissing from a view to one that has a list, and that list is empty, you will get a crash without the asynchronous delay. On the other hand, if you have even just one entry in that list view, forcing a loop iteration to generate the parent view, you'll see that the crash will not occur.

I'm not so sure how robust my solution of wrapping the dismiss call in a delay is. I have to test it much more. If you have ideas on this, please let me know! I'd be very happy to learn from you!

like image 145
Justin Ngan Avatar answered Sep 22 '22 00:09

Justin Ngan