Search code examples
listforeachswiftuiswiftui-navigationlink

SwiftUI - List / ForEach in combination with NavigationLink and isActive doesn't work properly


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!


Solution

  • 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