13

I'm trying to do a NavigationLink within a List or ForEach Loop in SwiftUI. Unfortunately I get a really weird behavior (e.g. when clicking on Leo it opens Karl, Opening Max points to Karl, too).

I've already figured out that it's related to the "isActive" attribute in the NavigationLink. Unfortunately, I need it to achieve a this behavior here: https://i.sstatic.net/g0BFz.gif which is also asked here SwiftUI - Nested NavigationView: Go back to root.

I also tried to work with selection and tag attribute but I wasn't able to achieve the "go back to root" mechanics.

Here's the Example:


import SwiftUI


struct Model: Equatable, Hashable {
    var userId: String
    var firstName: String
    var lastName: String
}


struct ContentView: View {
    
    @State var navigationViewIsActive: Bool = false
    
    var myModelArray: [Model] = [
        Model(userId: "27e880a9-54c5-4da1-afff-05b4584b1d2f", firstName: "Leo", lastName: "Test"),
        Model(userId: "1050412a-cb12-4160-b7e4-2702ab8430c3", firstName: "Max", lastName: "Test"),
        Model(userId: "1050412a-cb12-4160-b7e4-2702ab8430c3", firstName: "Karl", lastName: "Test")]
    
    var body: some View {
        NavigationView {
            List(myModelArray, id: \.self) { model in
                NavigationLink(destination: secondView(firstName: model.firstName), isActive: $navigationViewIsActive){ Text(model.firstName) }
            }
            .listStyle(PlainListStyle())
        }
    }
}

struct secondView: View {
    
    @State var firstName: String
    
    var body: some View {
        NavigationView {
            Text(firstName)
                .padding()
        }
    }
    
}

Thanks!

2 Answers 2

19

This happened because of the using of only one state navigationViewIsActive

So when you click in a navigation link , the value will change to True , and all the links will be active

The solution for this scenario is like that :

  • Define a new State which will hold the selected model value
  • You need just one NavigationLink , and make it Hidden (put it inside a VStack)
  • In the List use Button instead of NavigationLink
  • When a Button is clicked : first change the selectedModel value , than make the navigationLink active (true)

Like the code below (Tested with IOS 14) :

import SwiftUI


struct Model: Equatable, Hashable {
    var userId: String
    var firstName: String
    var lastName: String
}


struct ContentView: View {
    
    @State var navigationViewIsActive: Bool = false
    @State var selectedModel : Model? = nil
    
    var myModelArray: [Model] = [
        Model(userId: "27e880a9-54c5-4da1-afff-05b4584b1d2f", firstName: "Leo", lastName: "Test"),
        Model(userId: "1050412a-cb12-4160-b7e4-2702ab8430c3", firstName: "Max", lastName: "Test"),
        Model(userId: "1050412a-cb12-4160-b7e4-2702ab8430c3", firstName: "Karl", lastName: "Test")]
    
    var body: some View {
        NavigationView {
            VStack {
                VStack {
                    if selectedModel != nil {
                        NavigationLink(destination: SecondView(firstName: selectedModel!.firstName), isActive: $navigationViewIsActive){ EmptyView() }
                    }
                }.hidden()
                
                List(myModelArray, id: \.self) { model in
                    Button(action: {
                        self.selectedModel = model
                        self.navigationViewIsActive = true
                    }, label: {
                        Text(model.firstName)
                    })
                }
                .listStyle(PlainListStyle())
            }
            
            
        }
    }
}

struct SecondView: View {
    
    @State var firstName: String
    
    var body: some View {
        NavigationView {
            Text(firstName)
                .padding()
        }
    }
    
}

struct Test_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

PS : I wrote this : How to navigate with SwiftUI , it will help you to understand the ways to navigate in swiftUI

Sign up to request clarification or add additional context in comments.

6 Comments

This fixes a bug in iOS 14.5 - Thanks
Didn't work for me. Instead it crashed! I'm assuming you meant to pass the "navigationViewIsActive" binding into the second view and then set to false from the second view.
There's also the bound selection option but I couldn't get that to work properly either. For some reason it runs very slow and in any case I could not get it to close the view after it's been opened. "init<V>(destination: Destination, tag: V, selection: Binding<V?>, label: () -> Label) where V : Hashable"
Thanks - great answer. Might also add that in my case, it helped to wrap the trigger point: self.navigationViewIsActive = true explicitly inside a withAnimation block as, sometimes, the incoming view failed to animate (obviously some bug).
This works, but there seems to be no animation on the very first click on the list item. After the first click, all items have animation
|
2

You don't need isActive in this case, just use

List(myModelArray, id: \.self) { model in
    NavigationLink(destination: secondView(firstName: model.firstName)) { 
       Text(model.firstName) 
    }
}

and you have not use NavigationView in second view in this, ie.

struct secondView: View {
    
    var firstName: String   // you don't need state here as well
    
    var body: some View {
        Text(firstName)
            .padding()
    }
}

3 Comments

But if I don't use isActive in this case how am I supposed to go back to the initial List from my second or third view? As from what I have researched the proper way is to use isActive and then set it to false from the third view so that the navigation flow collapses.
There is nothing about this wanted behavior in your question.
Hmm sorry, didn't write it precisely enough. I wanted to express this requirement with this statement in my question "I need it to achieve a this behavior here: i.sstatic.net/g0BFz.gif ". Any ideas if this is possible?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.