How it works
SpatialTapGesture recognizes one or more taps and, unlike the simpler tap modifiers, reports exactly where the tap landed. Each recognized tap produces a SpatialTapGesture.Value carrying the contact location in the coordinate space you specify, so you can react to the position of a touch rather than just its occurrence. Reach for it when your view needs to know the point that was tapped — placing a marker, sampling a coordinate, or hit-testing against custom geometry — instead of merely toggling state on any tap.
Create the recognizer with SpatialTapGesture()
The initializer constructs a gesture that fires after a given number of taps. The default recognizes a single tap; pass a
countto require a double- or triple-tap before the gesture succeeds. The example uses the bareSpatialTapGesture()form to react to each single tap of the box.Read the contact point from value.location
When the gesture succeeds it delivers a
SpatialTapGesture.Valuewhoselocationis the tap's position as aCGPoint. This is the distinguishing feature of the gesture — it tells you where, not just whether, the tap happened. The example readsvalue.locationand stores it in thelocationstate so a whiteCirclecan be drawn at that exact spot.Respond with onEnded
Because a tap is discrete, you handle it through
onEnded, which runs once the required taps are recognized and hands you the finalvalue. In the example theonEndedclosure assignsvalue.locationand setstapped = true, which flips the rectangle's fill and updates the caption text.Attach it with the gesture(_:) modifier
A gesture only takes effect once it's bound to a view via the
.gesture(_:)modifier, which installs it on that view's interactive region. Here theSpatialTapGestureis attached to theRoundedRectangle, so taps anywhere within its frame are reported relative to that view.Pin the location to a coordinate space
The reported
locationis expressed in a coordinate space; SpatialTapGesture defaults to the local space of the view it's attached to, and you can request another by passingcoordinateSpace:to the initializer. The example relies on the local default, which is whyvalue.locationlines up directly with theCircle's.position(location)inside the same rectangle.
SpatialTapGesture() to SpatialTapGesture(count: 2) so the marker only moves on a double-tap, demonstrating how count gates when the gesture's onEnded fires.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct SpatialTapGestureDemo: View {
@State private var location: CGPoint = .zero
@State private var tapped = false
var body: some View {
VStack(spacing: 16) {
RoundedRectangle(cornerRadius: 12)
.fill(tapped ? Color.green : Color.blue)
.frame(width: 220, height: 140)
.overlay(
Circle()
.fill(.white)
.frame(width: 16, height: 16)
.position(location)
)
.gesture(
SpatialTapGesture()
.onEnded { value in
location = value.location
tapped = true
}
)
Text(tapped ? "Tapped at (\(Int(location.x)), \(Int(location.y)))" : "Tap the box")
.font(.callout)
}
.padding()
}
}