'How to perform an action after NavigationLink is tapped?

I have a Plus button in my first view. Looks like a FAB button. I want to hide it after I tap some step wrapped in NavigationLink. So far I have something like this:

ForEach(0 ..< 12) {item in
    NavigationLink(destination: TransactionsDetailsView()) {
        VStack {
            HStack(alignment: .top) {
                Text("List item")
            }
            .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
            .foregroundColor(.black)
            Divider()
        }
    }
    .simultaneousGesture(TapGesture().onEnded{
        self.showPlusButton = false
    })
        .onAppear(){
            self.showPlusButton = true
    }
}

It works fine with single tap. But when I long press NavigationLink it doesn't work. How should I rewrite my code to include long press as well? Or maybe I should make it work different than using simultaneousGesture?



Solution 1:[1]

Yes, NavigationLink does not allow such simultaneous gestures (might be as designed, might be due to issue, whatever).

The behavior that you expect might be implemented as follows (of course if you need some chevron in the list item, you will need to add it manually)

struct TestSimultaneousGesture: View {
    @State var showPlusButton = false
    @State var currentTag: Int?
    var body: some View {

        NavigationView {
            List {
                ForEach(0 ..< 12) { item in
                    VStack {
                        HStack(alignment: .top) {
                            Text("List item")
                            NavigationLink(destination: Text("Details"), tag: item, selection: self.$currentTag) {
                                EmptyView()
                            }
                        }
                        .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
                        .foregroundColor(.black)
                        Divider()
                    }
                    .simultaneousGesture(TapGesture().onEnded{
                        print("Got Tap")
                        self.currentTag = item
                        self.showPlusButton = false
                    })
                    .simultaneousGesture(LongPressGesture().onEnded{_ in
                        print("Got Long Press")
                        self.currentTag = item
                        self.showPlusButton = false
                    })
                    .onAppear(){
                        self.showPlusButton = true
                    }
                }
            }
        }
    }
}

backup

Solution 2:[2]

I'm using the following code. I prefer it to just NavigationLink by itself because it lets me reuse my existing ButtonStyles.


struct NavigationButton<Destination: View, Label: View>: View {
    var action: () -> Void = { }
    var destination: () -> Destination
    var label: () -> Label

    @State private var isActive: Bool = false

    var body: some View {
        Button(action: {
            self.action()
            self.isActive.toggle()
        }) {
            self.label()
              .background(
                ScrollView { // Fixes a bug where the navigation bar may become hidden on the pushed view
                    NavigationLink(destination: LazyDestination { self.destination() },
                                                 isActive: self.$isActive) { EmptyView() }
                }
              )
        }
    }
}

// This view lets us avoid instantiating our Destination before it has been pushed.
struct LazyDestination<Destination: View>: View {
    var destination: () -> Destination
    var body: some View {
        self.destination()
    }
}

And to use it:

var body: some View {
  NavigationButton(
    action: { print("tapped!") },
    destination: { Text("Pushed View") },
    label: { Text("Tap me") }
  )
}

Solution 3:[3]

I have tried an alternative approach to solving my problem. Initially I didn't use "List" because I had a problem with part of my code. But it cause another problem: PlusButton not disappearing on next screen after tapping NavigationLink. This is why I wanted to use simultaneousGesture - after tapping a link some actions would be performed as well (here: PlusButton would be hidden). But it didn't work well.

I have tried an alternative solution. Using List (and maybe I will solve another problem later.

Here is my alternative code. simultaneousGesture is not needed at all. Chevrons are added automatically to the list. And PlusButton hides the same I wanted.

import SwiftUI

struct BookingView: View {

    @State private var show_modal: Bool = false

    var body: some View {
        NavigationView {
            ZStack {
                List {
                    DateView()
                        .listRowInsets(EdgeInsets())

                    ForEach(0 ..< 12) {item in
                        NavigationLink(destination: BookingDetailsView()) {
                            HStack {
                                Text("Booking list item")
                                Spacer()
                            }
                            .padding()
                        }
                    }
                }.navigationBarTitle(Text("Booking"))

                VStack {
                    Spacer()
                    Button(action: {
                        print("Button Pushed")
                        self.show_modal = true
                    }) {
                        Image(systemName: "plus")
                            .font(.largeTitle)
                            .frame(width: 60, height: 60)
                            .foregroundColor(Color.white)
                    }.sheet(isPresented: self.$show_modal) {
                        BookingAddView()
                    }
                    .background(Color.blue)
                    .cornerRadius(30)
                    .padding()
                    .shadow(color: Color.black.opacity(0.3), radius: 3, x: 3, y: 3)
                }
            }

        }
    }

}

Solution 4:[4]

Another alternative I have tried. Not using simultaneousGesture, but an onDisappear modifier instead. Code is simple and It works. One downside is that those actions happen with a slight delay. Because first the destination view slides in and after this the actions are performed. This is why I still prefer @Asperi's answer where he added .simultaneousGesture(LongPressGesture) to my code.

ForEach(0 ..< 12) {item in
    NavigationLink(destination: TransactionsDetailsView()) {
        VStack {
            HStack(alignment: .top) {
                Text("List item")
            }
            .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
            .foregroundColor(.black)
            Divider()
        }
    }
    .onDisappear(){
        self.showPlusButton = false
    }
    .onAppear(){
        self.showPlusButton = true
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 mallow
Solution 4 mallow