Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.stack.imgur.com/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!

like image 505
SwiftUIRookie Avatar asked Dec 21 '20 18:12

SwiftUIRookie


2 Answers

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

like image 54
Ouail Bellal Avatar answered Nov 01 '22 22:11

Ouail Bellal


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()
    }
}
like image 2
Asperi Avatar answered Nov 01 '22 22:11

Asperi