JAHelia
JAHelia

Reputation: 7902

SwiftUI: NavigationLink is always activated when in a List

I can't prevent SwiftUI's NavigationLink from being activated when in a List, I have this simple piece of code in which I need to do some kind of business check before deciding to show the details page or not (in a real world app, there might be some business logic happens inside the button's action):

struct ContentView: View {
    @State var showDetail = false
    var body: some View {
        NavigationView {
            List {
                Text("Text 1")
                Text("Text 2")
                Text("Text 3")
                NavigationLink(destination: DetailView(), isActive: $showDetail) {
                    LinkView(showDetails: $showDetail)

                }
            }
        }
    }
}

struct LinkView: View {
    @Binding var showDetails: Bool

    var body: some View {
        Button(action: {
            self.showDetails = false
        }) {
            Text("Open Details")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

how can I prevent navigation link from opening the details page in this case ? and is this a bug in the SDK ?

p.s. XCode version: 13.3.1 and iOS version (real device): 13.3.1

Edit

I can't replace List with ScrollView because I have a ForEach list of items in my real app, so don't post an answer considering using ScrollView.

Upvotes: 4

Views: 7620

Answers (3)

Mamoon Yameen
Mamoon Yameen

Reputation: 389

If you use .disable(true) it will reduce your list item opacity like a disabled button, to prevent this. use below code style. Use Navigation Link in backGround and check your navigation condition on Tap Gesture of your view.

VStack{
        List(0..<yourListArray.count, id: \.self) { index in  

        {
            Text("\(yourListArr[index].firstName)")
        }().onTapGesture{
            let jobType = getFlags(jobsArr: yourListArray, index:index) 
           if jobType.isCancelledFlag == true{
               self.shouldNavigate = false
           }else{
               self.shouldNavigate = true
           }
        }//Tap Gesture End
        .background(NavigationLink(destination: YourDestinationView(),isActive: self.$shouldNavigate) {
         }.hidden())}}//vStack

Upvotes: 0

user3441734
user3441734

Reputation: 17534

in a real world app, there might be some business logic happens inside the button's action

seems to be a little bit alogical.You can simply conditionally disable the link (and inform the user, that the link is unavailable by visual appearance)

NavigationLink(...).disabled(onCondition)

where

func disabled(_ disabled: Bool) -> some View

Parameters

disabled

A Boolean value that determines whether users can interact with this view.

Return Value

A view that controls whether users can interact with this view.

Discussion

The higher views in a view hierarchy can override the value you set on this view. In the following example, the button isn’t interactive because the outer disabled(_:) modifier overrides the inner one:

HStack {
    Button(Text("Press")) {}
    .disabled(false)
}
.disabled(true)

Upvotes: 10

Asperi
Asperi

Reputation: 257493

If I correctly understood your goal, it can be as follows

List {
    Text("Text 1")
    Text("Text 2")
    Text("Text 3")
    LinkView(showDetails: $showDetail)
        .background(
            NavigationLink(destination: DetailView(), isActive: $showDetail) { EmptyView() })
}

and

struct LinkView: View {
    @Binding var showDetails: Bool

    var body: some View {
        Button(action: {
            self.showDetails = true // < activate by some logic
        }) {
            Text("Open Details")
        }
    }
}

Upvotes: 1

Related Questions