I would like to be able to detect if there are any views that fall under the location of a DragGesture.
I have tried adding DragGesture to each view (circle in this case) hoping the action would transfer across views for a continuous DragGesture motion. But that didn't work.
Surely there is a way?
import SwiftUI
struct ContentView2: View {
var body: some View {
ZStack {
Image(.background)
.resizable()
.scaledToFill()
.ignoresSafeArea()
.gesture(
DragGesture(minimumDistance: 5)
.onChanged { value in
print ("DG detetected at \(value.location.x) : \(value.location.y)")
})
HStack {
Circle()
.fill(.red)
.frame(width: 100, height: 100)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
print ("DG in red")
})
Circle()
.fill(.white)
.frame(width: 100, height: 100)
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
}
}
}
}
#Preview {
ContentView2()
}
I would suggest saving the drag location in a GestureState
variable. Then you can use a GeometryReader
behind each of the circles to detect whether the drag location is inside the circle.
It is important that the drag gesture and the GeometryReader
are referring to the same coordinate space.
A GestureState
variable is automatically reset at the end of drag, so there is no need to reset it manually. In the example below, the only other variable that needs to be reset when drag ends is the text info.
struct ContentView: View {
@GestureState private var dragLocation = CGPoint.zero
@State private var dragInfo = " "
private func dragDetector(for name: String) -> some View {
GeometryReader { proxy in
let frame = proxy.frame(in: .global)
let isDragLocationInsideFrame = frame.contains(dragLocation)
let isDragLocationInsideCircle = isDragLocationInsideFrame &&
Circle().path(in: frame).contains(dragLocation)
Color.clear
.onChange(of: isDragLocationInsideCircle) { oldVal, newVal in
if dragLocation != .zero {
dragInfo = "\(newVal ? "entering" : "leaving") \(name)..."
}
}
}
}
var body: some View {
ZStack {
Color(white: 0.2)
VStack(spacing: 50) {
Text(dragInfo)
.foregroundStyle(.white)
HStack {
Circle()
.fill(.red)
.frame(width: 100, height: 100)
.background { dragDetector(for: "red") }
Circle()
.fill(.white)
.frame(width: 100, height: 100)
.background { dragDetector(for: "white") }
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.background { dragDetector(for: "blue") }
}
}
}
.gesture(
DragGesture(coordinateSpace: .global)
.updating($dragLocation) { val, state, trans in
state = val.location
}
.onEnded { val in
dragInfo = " "
}
)
}
}
For versions of iOS before iOS 17, you would need to use the version of .onChange
that only takes 1 parameter (just omit the parameter oldVal
).