As you have found out, LongPressGesture ends when the time interval required to trigger it has elapsed, instead of when the user has lifted their finger. Therefore, it is unsuitable for detecting the finger lifting.
I would use a DragGesture instead. Its onChanged is called when the gesture starts, and its onEnded is called when the finger lifts. We can record the start time in onChanged, and end time in onEnded, and hence how long the user has been pressing down.
@State var touchDownTime: Date?
@State var impactTrigger = false
var body: some View {
Color.yellow
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
if touchDownTime == nil {
touchDownTime = value.time
impactTrigger.toggle()
print("Started")
}
})
.onEnded({ value in
if let touchDownTime,
value.time.timeIntervalSince(touchDownTime) >= 1 {
impactTrigger.toggle()
print("Ended")
}
self.touchDownTime = nil
})
)
.sensoryFeedback(.impact(weight: .light), trigger: impactTrigger)
}
Note that I have changed it to use sensoryFeedback to create the haptic feedback. If you are targeting an older version than iOS 17, using UIImpactFeedbackGenerator is fine too.
Note that unlike a LongPressGesture, which doesn’t trigger when the user moves their finger too much after pressing down, DragGesture is still recognised if the finger moves. If this is undesirable, use the value.translation property to determine whether the finger has moved too much.
@State var shouldCancel = false
...
.onChanged({ value in
...
let threshold: CGFloat = 10 // decide a threshold
if hypot(value.translation.width, value.translation.height) > threshold {
shouldCancel = true
}
})
.onEnded({ value in
if let touchDownTime,
!shouldCancel, // <----
value.time.timeIntervalSince(touchDownTime) >= 1 {
impactTrigger.toggle()
print("Ended")
}
self.touchDownTime = nil
shouldCancel = false
})




