I want to use a long press gesture and detect when the user has been holding for 1 consecutive second, and I also want to detect when the user lets go of the screen. Currently the onChanged detects when the gesture begins and the onEnded fires after 1 second. So I can use the onEnded to detect when the user has been holding for 1 consecutive second. But how can I know when the user lets go?
Color.blue
.simultaneousGesture(LongPressGesture(minimumDuration: 1.0)
.onChanged { _ in
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
.onEnded { _ in
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
)
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
})