I am working on a custom button component for which I need to use drag gesture to set various states of custom animation, below is simplified code of where I am at the moment. Problem here is that action associated to the button gets executed during .onEnded
phase. This means that user can drag their finger outside of the button and upon releasing it action will be called. I'd like to detect if finger is in bounds of the button somehow and if user drags it out, not execute action and toggle taping state to off, essentially canceling the tap when finger is not on the button.
I tried wrapping GeometryReader
around my HStack
, but it reported weird values (much higher values than what the real size of the button is) and it made button component expand to full screen width and height.
HStack {
Text("Sample Text")
}
.scaleEffect(taping ? 0.9 : 1)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { pressed in
taping.toggle()
}
.onEnded { pressed in
taping.toggle()
// Execute tap action here
}
)
I am able to detect if the finger is outside of the element or not using GeometryReader, but can't figure out how to forcefully end drag gesture. Please try below codes.
Using PreferenceKey to get view's frame:
struct ViewPreferenceKey: PreferenceKey {
typealias Value = CGRect
static var defaultValue: CGRect = CGRect()
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
Checking drag location using GeometryReader:
HStack {
Text("Sample Text")
.border(Color.black, width: 1)
}
.scaleEffect(taping ? 0.9 : 1)
.background(
GeometryReader { reader in
let frame = reader.frame(in: .local)
Rectangle()
.fill(Color.clear)
.preference(key: ViewPreferenceKey.self, value: frame)
}
)
.onPreferenceChange(ViewPreferenceKey.self) { value in
bounds = value
print("\(bounds)")
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { pressed in
if bounds.contains(pressed.location) {
print("Inside")
} else {
print("Outside")
}
taping = true
}
.onEnded { _ in
taping = false
}
)
You may manually invalidate the gesture once drag location is outside the view:
DragGesture(minimumDistance: 0)
.onChanged { pressed in
if bounds.contains(pressed.location) && gestureInvalidated == false {
print("Inside")
taping = true
} else {
print("Outside")
taping = false
gestureInvalidated = true
}
}
.onEnded { _ in
taping = false
gestureInvalidated = false
}