andras
andras

Reputation: 3645

SwiftUI NavigationLink with constant binding for isActive

I don't understand why SwiftUI NavigationLink's isActive behaves as if it has it's own state. Even though I pass a constant to it, the back button overrides the value of the binding once pressed.

Code:


import Foundation
import SwiftUI

struct NavigationLinkPlayground: View {

    @State
    var active = true

    var body: some View {

        NavigationView {
            VStack {
                Text("Navigation Link playground")
                Button(action: { active.toggle() }) {
                    Text("Toggle")
                }

                Spacer()
                        .frame(height: 40)

                FixedNavigator(active: active)
            }
        }
    }

}

fileprivate struct FixedNavigator: View {

    var active: Bool = true

    var body: some View {
        return VStack {
            Text("Fixed navigator is active: \(active)" as String)

            NavigationLink(
                    destination: SecondScreen(),
                    // this is technically a constant!
                    isActive: Binding(
                            get: { active },
                            set: { newActive in print("User is setting to \(newActive), but we don't let them!") }
                    ),
                    label: { Text("Go to second screen") }
            )
        }
    }

}

fileprivate struct SecondScreen: View {

    var body: some View {
        Text("Nothing to see here")
    }

}


This is a minimum reproducible example, my actual intention is to handle the back button press manually. So when the set inside the Binding is called, I want to be able to decide when to actually proceed. (So like based on some validation or something.)

And I don't understand what is going in and why the back button is able to override a constant binding.

Upvotes: 0

Views: 3553

Answers (1)

Mahdi BM
Mahdi BM

Reputation: 2104

Your use of isActive is wrong. isActive takes a binding boolean and whenever you set that binding boolean to true, the navigation link gets activated and you are navigated to the destination.
isActive does not control whether the navigation link is clickable/disbaled or not. Here's an example of correct use of isActive. You can manually trigger the navigation to your second view by setting activateNavigationLink to true.

EDIT 1:

In this new sample code, you can disable and enable the back button at will as well:

struct ContentView: View {
    
    @State var activateNavigationLink = false
    
    var body: some View {
        NavigationView {
            VStack {
                // This isn't visible and should take 0 space from the screen!
                // Because its `label` is an `EmptyView`
                // It'll get programmatically triggered when you set `activateNavigationLink` to `true`.
                NavigationLink(
                    destination: SecondScreen(),
                    isActive: $activateNavigationLink,
                    label: EmptyView.init
                )
                
                Text("Fixed navigator is active: \(activateNavigationLink)" as String)
                
                Button("Go to second screen") {
                    activateNavigationLink = true
                }
            }
        }
    }
}

fileprivate struct SecondScreen: View {
    
    @State var backButtonActivated = false
    
    var body: some View {
        VStack {
            Text("Nothing to see here")
            
            Button("Back button is visible: \(backButtonActivated)" as String) {
                backButtonActivated.toggle()
            }
        }
        .navigationBarBackButtonHidden(!backButtonActivated)
    }
}

Upvotes: 2

Related Questions