ios – Swift UI detect start and end of gesture


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
})

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img