9
votes

I can't use multiple NavigationLinks in the same row of a List.

It looks like the navigation stack is totally messed up, because you tap once and it goes to multiple views and back erratically...

in TestList, I've tried adding the separate NavigationLinks in Sections, and I've tried moving the NavigationLinks two different places in the view hierarchy...

I've tried adding two NavigationViews for each row of the list, but then the navigationTitleBar don't go away when I need it to..


struct ContentView: View {
    var body: some View {

        NavigationView {
            TestList()
        }


    }
}


struct TestList: View {
    var body: some View {

        List  {
            ListCellView()

        }

    }
}


struct ListCellView: View {

    var body: some View {
        VStack {
            Spacer()

            NavigationLink(destination: TestDestination1())  {
                Text("Test Destination 1")
                    .frame(width: 140, height: 50)
                    .background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
            }

            Spacer()

            NavigationLink(destination: TestDestination2())  {
                Text("Test Destination 2")
                    .frame(width:140, height: 50)
                    .background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.purple, lineWidth: 3.0))
                Spacer()
            }
        }
    }
}


struct TestDestination1: View {
    var body: some View {
        Text("Test Destination 1")
    }
}

struct TestDestination2: View {
    var body: some View {
        Text("Test Destination 2")
    }
}


I expect that when you tap a NavigationLink, it will navigate to the destination view.

What happens is when two NavigationLinks are in the same row of a List and you tap in it, it will: 1. go to one of the views 2. After tapping 'back', it will take you back to the view AND THEN take you to the other destination view.

https://youtu.be/NCTnqjzJ4VE

2
It seems to be a pattern in SwiftUI that things work great if you stick to defaults, but if you do something unexpected, things get very messy very fast. If it's at all possible to break the two NavigationLinks into two list items, I would do that.Benjamin Kindle
There is nothing wrong with the navigation stack. What kind of behaviour are you trying to achieve? You wouldn't expect tapping on a cell in UITableView to navigate to two different views at the same time, would you?LuLuGaGa
@LuLuGaGa That's a good point... I was trying to have multiple views in a List row. I will try to split it up like @Benjamin\ Kindle suggests.nSquid
@BenjaminKindle Yes I think it may be possible. That may be the solution.nSquid

2 Answers

11
votes

As the others mentioned, why 2 NavigationLinks in 1 cell. The issue is with multiple buttons and gesture in general for the Cell. I guess it is expected 1 Button/NavigationLink max per cell. As you noticed, on your video, you tap on a NavigationLink but your full Cell got the gesture (highlighted), which in return impact the other Buttons/NavigationLinks.

Anyhow, you can have it working, the 2 NavigationLinks in 1 cell, with a hack. Below I have created SGNavigationLink, which I use for my own app which solve your issue. It simply replace NavigationLink and based on TapGesture, so you will lose the highlight.

NB: I slightly modified your ListCellView, as the Spacer within my SGNavigationLink was creating an internal crash.

struct ListCellView: View {

    var body: some View {
        VStack {

            HStack{
                SGNavigationLink(destination: TestDestination1())  {
                    Text("Test Destination 1")
                        .frame(width: 140, height: 50)
                        .background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
                }

                Spacer()
            }

            HStack{
            SGNavigationLink(destination: TestDestination2())  {
                Text("Test Destination 2")
                    .frame(width:140, height: 50)
                    .background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.purple, lineWidth: 3.0))

            }
            Spacer()
            }
        }
    }
}



struct SGNavigationLink<Content, Destination>: View where Destination: View, Content: View {
    let destination:Destination?
    let content: () -> Content


    @State private var isLinkActive:Bool = false

    init(destination: Destination, title: String = "", @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.destination = destination
    }

    var body: some View {
        return ZStack (alignment: .leading){
            if self.isLinkActive{
                NavigationLink(destination: destination, isActive: $isLinkActive){Color.clear}.frame(height:0)
            }
            content()
        }
        .onTapGesture {
            self.pushHiddenNavLink()
        }
    }

    func pushHiddenNavLink(){
        self.isLinkActive = true
    }
}
3
votes

I am not sure about why do you need multiple Navigationlinks (duplicate code). You can use a data source that will hold the required properties of the list [title, color, id etc] and based on the id, call the desired View. Reuse the same code. Here is an example.

struct TestList: View {
    var body: some View {
        List { // <- Use Data source 
            ForEach(0..<2) { index in
                 ListCellView(index: index)
            }
        }
    }
}

struct ListCellView: View {
    var index: Int
    var body: some View {
         return   NavigationLink(destination: ViewFactory.create(index))  {
                Text("Test Destination 1")
                    .frame(width: 140, height: 50)
                    .background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
            }
    }
}


class ViewFactory {
   static func create(_ index: Int) -> AnyView {
        switch index {
        case 0:
            return AnyView(TestDestination1())
        case 1:
            return AnyView(TestDestination2())
        default:
           return AnyView(EmptyView())
        }
    }
}

struct TestDestination1: View {
    var body: some View {
        Text("Test Destination 1")
    }
}

struct TestDestination2: View {
    var body: some View {
        Text("Test Destination 2")
    }
}