3

I'm trying to implement a List of NavigationLinks that will allow me to navigate between views of up to 3 levels of navigation links.

Main View -> View2 -> View3 -> View4

When I reach View4, the view will not have the default "back" button at the navigation bar but will instead have a button that will allow me to navigate back to "Main View"

Using the solution mentioned here, I was able to (sort of) achieve what I wanted by making slight modifications in the solution so that the isActive bit is implemented as an array of isActive bits each corresponding to the number of navigation links that is iterated within the List container.

The only problem that I noticed though is that the pop-to-root view operation using the isActive bits will only work if I use it with the @State property wrapper but will do nothing if my isActive bits are contained within an ObservableObject class instance. My issue with using the @State wrapper is that I will have to pre-define a fixed array size and populate it with "false" bits which is not what I want since the number of items that I can iterate within the List container may vary so I want the isActive array size to be the same as the number of items that I'm iterating within the List container.

Here's the simplified implementation of the pop-to-root NavigationView code for reference. The top half of the NavigationLinks are the ones that use the fixed-size isActive bit array with the @State wrapper which works while the bottom half of the NavigationLinks are the ones that use the observable object which can have a flexible isActive bit array size but I can't seem to figure out why this approach won't work.

Main View:

struct ContentView: View {
    @ObservedObject var itemGroup: ItemGroup
    // If I use @State wrapper, I need to pre allocate a fixed array size and populate
    // it with 'false' values. 
    @State var isActive: [Bool] = [false, false, false, false]
    
    var body: some View {
        NavigationView {
            VStack {
                
            // MARK: Navigation links using isActive property with @State wrapper 
            // (this implementation works but the isActive array size needs to be defined
            // with a fixed size, I would prefer the size to be variable)
            List(1..<5) { item in
                NavigationLink(destination: View2(isActive: $isActive[item-1], sub: "sub\(item)"), isActive: $isActive[item-1]) {
                    Text("Go to view 2 using @State, sub\(item)")
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                }
            }
            
            // MARK: Navigation links using isActive property defined within an observable 
            // class object 
            // (I would have preferred this method since I can have the
            // flexibility of allocating an isActive array based on the number of
            // NavigationLink views generated by the list container but the pop-to-root
            // operation won't work with this approach)
            //  Note: The range 1..<5 is used here for illustration purposes only but is actually an array of objects whose array size may vary
            List(1..<5) { item in
                NavigationLink(destination: View2(isActive: $itemGroup.isActive[item-1], sub: "sub\(item)"), isActive: $itemGroup.isActive[item-1]) {
                    Text("Go to view 2, sub\(item)")
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                }
            }
            .navigationBarTitle("Main view")
            }   
        }   
    }
}

class ItemGroup: ObservableObject {
    @Published var isActive: [Bool] = []
    
    init() {
        //  Note: The range 1..<5 is used only for illustration purposes only.
        //        In my use case, this is actually an array of objects whose array size may vary.
        for _ in 1..<5 {
            isActive.append(false)
        }
    }
}

View2:

struct View2: View {
    @Binding var isActive: Bool
    @State var sub: String
    var body: some View {
        NavigationLink(destination: View3(isActive: $isActive)) {
            Text("Go to view 3")
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
        }
        .navigationBarTitle("View 2, \(sub)")
    }
}

View3:

struct View3: View {
    @Binding var isActive: Bool
    var body: some View {
        NavigationLink(destination: View4(isActive: $isActive)) {
            Text("Go to view 4")
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
        }
        .navigationBarTitle("View 3")
    }
}

View4 (this is the one with the pop-to-root view function):

struct View4: View {
    @Binding var isActive: Bool
    
    var body: some View {
        VStack {
            Text("View 4")
            Button("Back to Main View", action: {
                isActive = false
            })
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
        }
        .navigationBarHidden(true)
    }
}

Here are the gif demos of the two different implementations.

Navigation using isActive array with @State property wrapper (Click link)

Navigation using isActive array within an observable object (Click link)

Can anyone please help me figure out why the bottom half of the NavigationLinks won't work and what changes should I do to make it work?

Appreciate the help. Thanks.

  • 1
    I hope the person who downvoted my question would post the reason for downvoting it. As a new poster, I'd like to know what I did wrong to deserve the downvote so I will avoid repeating the same mistake in the future. – LooseyGoosey Apr 06 '21 at 02:18
  • The downvote wasn't mine, but I've upvoted to counter it. I believe your question is well-formed and clear. The issue centers around the relationship between the `ObservableObject` and `List` -- if you change `List` to `ForEach`, everything works as expected. I wish I had an easy fix, but I haven't found something that reliably works. I'll keep looking, though. – jnpdx Apr 06 '21 at 04:01
  • Thanks @jnpdx for the upvote and for informing me that ObservableObject and List do not mix well. I'm not aware about this behavior but I'll read up on this to find out why. I think I can work with using ForEach instead of List for now. To account for the missing ">" graphic that the List container provides, I can probably just include a SF symbol image for ">" to simulate how a List container would look like. I'll keep on monitoring for an easier workaround or fix that you will be able to find in the future. Appreciate the help. – LooseyGoosey Apr 06 '21 at 04:31

0 Answers0