Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Picker crashes when displayed via NavigationLink

Does anyone have a workaround for the following crash?

I have a form that is displayed via NavigationLink in a parent Navigation Controller like so:

    var body: some View {
        NavigationView {
            NavigationLink(destination: PickerView()) {
                Text("Picker View")
            }
        }
    }

The PickerView has three pickers. The first determines which of the others is shown:

struct PickerView: View {
    @State var sectionValue = "pet"
    @State var petValue = "dog"
    @State var fruitValue = "apple"
    @State var foodValue = "pasta"
    var body: some View {

        Form {

            Picker(selection: $sectionValue, label: Text("What is your favorite?")) {
                Text("Pet").tag("pet")
                Text("Fruits").tag("fruits")
                Text("Foods").tag("foods")
            }

            if (sectionValue == "pet") {
                Picker(selection: $petValue, label: Text("Favorite pet")) {
                    Text("Dog").tag("dog")
                    Text("Cat").tag("cat")
                    Text("Lizard").tag("lizard")
                }
            } else if (sectionValue == "fruits") {
                Picker(selection: $fruitValue, label: Text("Favorite fruit")) {
                    Text("Apple").tag("apple")
                    Text("Pear").tag("pear")
                    Text("Orange").tag("orange")
                }
            } else if (sectionValue == "foods") {
                Picker(selection: $foodValue, label: Text("Favorite food")) {
                    Text("Pasta").tag("pasta")
                    Text("Ice Cream").tag("ice_cream")
                    Text("Bacon").tag("bacon")
                }
            }
        }
    }
}

In the iOS 13.3 simulator (and device), I see the following behavior: Navigating to the PickerView and selecting the alternate value for the first picker will hide the 2nd picker and show the 3rd picker, as expected. However if you operate the 3rd picker, it will show with blank values... followed shortly by a crash.

The crash shows a stack trace with hundreds of calls to [UINavigationController _navigationBar:itemEnabledAutoScrollTransition:]

I think this is an Apple bug. I have filed FB7534235 but I wanted to see if anyone has any workarounds or suggestions?

One option is to use .disabled() to disable (rather than hide) the picker, but this results in a more confusing user interface.

Side note: This appears to be an interaction with NavgiationView()/NagivationLink() and Picker() -- because if you comment out the NagivationLink and render the PickerView directly in the NavigationView, everything works as expected without any crashing.

update: Sample case updated to make it a 3-way choice for the sub-pickers.. Thanks to @krjw below pointing out that in the two-way case, an "else if" rather than two separate if-statements can yield the desired behavior without a crash... though I'm still not sure why (unless it is, "just a bug")

like image 522
Jake Avatar asked Jan 14 '20 12:01

Jake


People also ask

What are the use cases for navigationlink in SwiftUI?

In this article, we saw the following use cases for NavigationLink in SwiftUI: Programmatic dismissing of views. Removing default navigation back button. Using NavigationLink with other view types. That’s it for this one.

How to avoid swiping or tapping the back button in SwiftUI?

When there is a slew of navigation screens, swiping or tapping the back button on each of them doesn’t really give the best user experience. Thankfully, we can avoid that by leveraging the isActive property of the NavigationLink in SwiftUI.

How do I get back to the root view in SwiftUI?

The goal is simple: For a navigation stack like ContentView -> View1 -> View2 -> View3, we need to get back to ContentView from View3. As explained in this Stack Overflow post, to pop to the root SwiftUI view, we need to set an isDetailLink (false) modifier on each NavigationLink.

Can a navigationview have a navigationlink inside it?

In doing so, we ensure that the detail views aren’t presented as a separate “detail” component of the containing NavigationView. By default, a ContextMenu, Toolbar, or other types of view presentation cannot contain a NavigationLink inside it.


Video Answer


1 Answers

I tested this with Xcode 11.3.1 on an iPad Pro running iPadOS 13.3. I got a strange reloading behaviour when choosing something in the first picker and then choosing again, but no crash.

I could replicate the crash on my iPhone.

Adding an else on the second if clause fixes that!

So the solution is the following code:

struct PickerView: View {
    @State var sectionValue = "phonetic"
    @State var phoneticValue = "alpha"
    @State var fruitValue = "apple"

    var body: some View {

        Form {

            Picker(selection: $sectionValue, label: Text("Pick a Section")) {
                Text("Phonetic Alphabet").tag("phonetic")
                Text("Fruits").tag("fruits")
            }

            if (sectionValue == "phonetic") {
                Picker(selection: $phoneticValue, label: Text("Pick a letter")) {
                    Text("Alpha").tag("alpha")
                    Text("Bravo").tag("bravo")
                    Text("Charlie").tag("charlie")
                }
            }
            else if (sectionValue == "fruits") {
                Picker(selection: $fruitValue, label: Text("Pick a fruit")) {
                    Text("Apple").tag("apple")
                    Text("Pear").tag("pear")
                    Text("Orange").tag("orange")
                }
            }
        }
    }
}

struct ContentView: View {

    var body: some View {
        NavigationView {
            NavigationLink(destination: PickerView()) {
                Text("Picker View")
            }
        }
    }
}

I hope this helps!

like image 155
krjw Avatar answered Sep 18 '22 23:09

krjw