1
votes

In SwiftUI I couldn't find a way to detect when the user taps on the default back button of the navigation view when I am inside DetailView1 in this code:

struct RootView: View {
    @State private var showDetails: Bool = false
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView1(), isActive: $showDetails) {
                    Text("show DetailView1")
                }
            }
            .navigationBarTitle("RootView")
        }
    }
}

struct DetailView1: View {
    @State private var showDetails: Bool = false
    var body: some View {
        NavigationLink(destination: DetailView2(), isActive: $showDetails) {
            Text("show DetailView2")
        }
        .navigationBarTitle("DetailView1")
    }
}

struct DetailView2: View {
    var body: some View {
        Text("")
            .navigationBarTitle("DetailView2")
    }
}

Using .onDisappear doesn't solve the problem as its closure is called when the view is popped off or a new view is pushed.

3
What would you like to do after you detect it? When the user taps the Back button, your DetailView1 view will be dismissed and your showDetails state variable will reset to false – maybe this is key you are looking for? - markiv
showDetails changed back to false after .onDisappear, not before, so it cannot be used for early back button tap detection. Just in case. - Asperi

3 Answers

2
votes

Following up on my comment, I would react to changes in the state of showDetails. Unfortunately didSet doesn't appear to trigger with @State variables. Instead, we can use an observable view model to hold the state, which does allow us to do intercept changes with didSet.

struct RootView: View {
    class ViewModel: ObservableObject {
        @Published var showDetails = false {
            didSet {
                debugPrint(showDetails)
                // Maybe do something here?
            }
        }
    }
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView1(), isActive: $viewModel.showDetails) {
                    Text("show DetailView1")
                }
            }
            .navigationBarTitle("RootView")
        }
    }
}
2
votes

An even nicer (SwiftUI-ier?) way of observing the published showDetails property:

struct RootView: View {
    class ViewModel: ObservableObject {
        @Published var showDetails = false
    }
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView1(), isActive: $viewModel.showDetails) {
                    Text("show DetailView1")
                }
            }
            .navigationBarTitle("RootView")
            .onReceive(self.viewModel.$showDetails) { isShowing in
                debugPrint(isShowing)
                // Maybe do something here?
            }
        }
    }
}
1
votes

The quick solution is to create a custom back button because right now the framework have not this possibility.

struct DetailView : View {

    @Environment(\.presentationMode) var mode: Binding<PresentationMode>

    var body : some View {
        Text("Detail View")
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: Button(action : {
                self.mode.wrappedValue.dismiss()
            }){
                Image(systemName: "arrow.left")
            })
    }
}