How can I make a NavigationLink with TapGesture and LongPressGesture working simultaneously together in SwiftUI?
The solution is to separate link with gestures, making link activated programmatically. In such cases actions, gestures, and scrolling do not conflict.
Here is a simplified demo of possible approach. Tested with Xcode 12.4 / iOS 14.4
struct ContentView: View {
var body: some View {
NavigationView {
List(0..<10) {
LinkCard(number: $0 + 1)
}
}
}
}
struct LinkCard: View {
let number: Int
@State private var isActive = false
@State private var isBig = false
var body: some View {
RoundedRectangle(cornerRadius: 12).fill(Color.yellow)
.frame(height: isBig ? 400 : 100)
.overlay(Text("Card \(number)"))
.background(NavigationLink(
destination: Text("Details \(number)"),
isActive: $isActive) {
EmptyView()
})
.onTapGesture {
isActive.toggle() // << activate link !!
}
.onLongPressGesture {
isBig.toggle() // << alterante action !!
}
}
}
How to perform an action after NavigationLink is tapped?
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
}
}
}
}
}
}
How to get a function to run when you push a Navigation Link in SwiftUI
I can think of a couple solutions. First is to use onAppear
on your destination view. This is less code than the second solution, but I have seen issues with onAppear
being fired at unexpected times or more than once.
NavigationLink {
Success()
.navigationBarBackButtonHidden(true)
.onAppear {
print("Sending to Storage")
}
} label: {
Text("Finish")
}
The other solution is to bind a variable to the isActive
property of NavigationLink
, and then use onChange(of:perform:)
to watch for changes and trigger your function when the value changes to true
.
struct ContentView: View {
@State private var navIsActive = false
var body: some View {
HStack {
Spacer()
NavigationLink(isActive: $navIsActive) {
Success()
.navigationBarBackButtonHidden(true)
} label: {
Text("Finish")
}
Spacer()
}
.onChange(of: navIsActive) { newValue in
if newValue {
print("Sending to Storage")
}
}
}
}
SwiftUI LongPressGesture takes too long to recognize when TapGesture also present
To having some multi gesture that fits every ones needs in projects, Apple has nothing offer than normal gesture, mixing them together to reach the wished gesture some times get tricky, here is a salvation, working without issue or bug!
Here a custom zero issue gesture called interactionReader, we can apply it to any View. for having LongPressGesture and TapGesture in the same time.
import SwiftUI
struct ContentView: View {
var body: some View {
Circle()
.fill(Color.yellow)
.frame(width: 150, height: 150)
.interactionReader(longPressSensitivity: 250, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: true)
.animation(Animation.easeInOut(duration: 0.2))
}
func tapAction() { print("tap action!") }
func longPressAction() { print("longPress action!") }
}
struct InteractionReaderViewModifier: ViewModifier {
var longPressSensitivity: Int
var tapAction: () -> Void
var longPressAction: () -> Void
var scaleEffect: Bool = true
@State private var isPressing: Bool = Bool()
@State private var currentDismissId: DispatchTime = DispatchTime.now()
@State private var lastInteractionKind: String = String()
func body(content: Content) -> some View {
let processedContent = content
.gesture(gesture)
.onChange(of: isPressing) { newValue in
currentDismissId = DispatchTime.now() + .milliseconds(longPressSensitivity)
let dismissId: DispatchTime = currentDismissId
if isPressing {
DispatchQueue.main.asyncAfter(deadline: dismissId) {
if isPressing { if (dismissId == currentDismissId) { lastInteractionKind = "longPress"; longPressAction() } }
}
}
else {
if (lastInteractionKind != "longPress") { lastInteractionKind = "tap"; tapAction() }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {lastInteractionKind = "none"}
}
}
return Group {
if scaleEffect { processedContent.scaleEffect(lastInteractionKind == "longPress" ? 1.5: (lastInteractionKind == "tap" ? 0.8 : 1.0 )) }
else { processedContent }
}
}
var gesture: some Gesture {
DragGesture(minimumDistance: 0.0, coordinateSpace: .local)
.onChanged() { _ in if !isPressing { isPressing = true } }
.onEnded() { _ in isPressing = false }
}
}
extension View {
func interactionReader(longPressSensitivity: Int, tapAction: @escaping () -> Void, longPressAction: @escaping () -> Void, scaleEffect: Bool = true) -> some View {
return self.modifier(InteractionReaderViewModifier(longPressSensitivity: longPressSensitivity, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: scaleEffect))
}
}
How to make whole List row a NavigationLink in SwiftUI
The problem comes with your 'longPressGesture' which wants to control the content of your row and listen for longPresses. To avoid this you can use 'onTapGesture' to control the activation of the NavigationLink and 'onLongPressGesture' to activate your actionSheet or Alert.
Here is a short code example that demonstrates the usage:
https://stackoverflow.com/a/67099257
Related Topics
React-Native iOS Podfile Issue with "Use_Native_Modules!"
Resize the Screen When Keyboard Appears
Pass in a Type to a Generic Swift Extension, or Ideally Infer It
Search Bar and Search Display Controller in Table View
Adding Unlimited Lines in a Text (Swiftui)
Passing Data Back from a Modal View in Watchkit
Typewriter Effect Text Animation
Swiftui Drag Gesture Across Multiple Subviews
Simulate Universal Gravitation for Two Sprite Kit Nodes
Uiimagepicker Cameraoverlayview Appears on Retake Screen
How to Draw a Point Using Core Graphics
How to Set Http Header Fields in Objective-C
Push Notification -Didfinishlaunchingwithoptions
Maximum Number of Peripherals on Corebluetooth
Ios: Is Core Graphics Implemented on Top of Opengl