Skip to content
Mobile
Skill

/swiftui-gestures

Implement, review, or improve SwiftUI gesture handling. Use when adding tap, long press, drag, magnify, or rotate gestures, composing gestures with simultaneously/sequenced/exclusively, managing transient state with @GestureState, resolving parent/child gesture conflicts with

From plugin
swift-ios-skills
1.3k86 skills1 MCP

Claude Code

$ npx -y skills add dpearson2699/swift-ios-skills --skill swiftui-gestures --agent claude-code

SKILL.md

swiftui-gestures.SKILL.md
name: swiftui-gestures
description: "Implement, review, or improve SwiftUI gesture handling. Use when adding tap, long press, drag, magnify, or rotate gestures, composing gestures with simultaneously/sequenced/exclusively, managing transient state with @GestureState, resolving parent/child gesture conflicts with highPriorityGesture or simultaneousGesture, building custom Gesture protocol conformances, or migrating from deprecated MagnificationGesture to MagnifyGesture or using the newer RotateGesture."

SwiftUI Gestures (iOS 26+)

Review, write, and fix SwiftUI gesture interactions. Apply modern gesture APIs with correct composition, state management, and conflict resolution using Swift 6.3 patterns.

**Scope boundary:** This skill owns SwiftUI gesture recognition, composition, gesture state, and gesture-specific accessibility alternatives. Broader SwiftUI architecture/state ownership belongs in `swiftui-patterns`; list, scroll, form, and control layout belongs in `swiftui-layout-components`; broad UIKit bridging belongs in `swiftui-uikit-interop`.

When correcting Apple API availability, deprecation, or behavior claims, cite the relevant Sosumi or official Apple documentation URL in the response.

Contents

  • [Gesture Overview](#gesture-overview)
  • [TapGesture](#tapgesture)
  • [LongPressGesture](#longpressgesture)
  • [DragGesture](#draggesture)
  • [MagnifyGesture (iOS 17+)](#magnifygesture-ios-17)
  • [RotateGesture (iOS 17+)](#rotategesture-ios-17)
  • [Gesture Composition](#gesture-composition)
  • [`@GestureState`](#gesturestate)
  • [Adding Gestures to Views](#adding-gestures-to-views)
  • [Custom Gesture Protocol](#custom-gesture-protocol)
  • [Common Mistakes](#common-mistakes)
  • [Review Checklist](#review-checklist)
  • [References](#references)

Gesture Overview

| Gesture | Type | Value | Since | |---|---|---|---| | `TapGesture` | Discrete | `Void` | iOS 13 | | `LongPressGesture` | Discrete | `Bool` | iOS 13 | | `DragGesture` | Continuous | `DragGesture.Value` | iOS 13 | | `MagnifyGesture` | Continuous | `MagnifyGesture.Value` | iOS 17 | | `RotateGesture` | Continuous | `RotateGesture.Value` | iOS 17 | | `SpatialTapGesture` | Discrete | `SpatialTapGesture.Value` | iOS 16 |

**Discrete** gestures fire once (`.onEnded`). **Continuous** gestures stream updates (`.onChanged`, `.onEnded`, `.updating`).

TapGesture

Recognizes one or more taps. Use the `count` parameter for multi-tap.

// Single, double, and triple tap
TapGesture()            .onEnded { tapped.toggle() }
TapGesture(count: 2)    .onEnded { handleDoubleTap() }
TapGesture(count: 3)    .onEnded { handleTripleTap() }

// Shorthand modifier
Text("Tap me").onTapGesture(count: 2) { handleDoubleTap() }

LongPressGesture

Succeeds after the user holds for `minimumDuration`. Fails if finger moves beyond `maximumDistance`.

// Basic long press (0.5s default)
LongPressGesture()
    .onEnded { _ in showMenu = true }

// Custom duration and distance tolerance
LongPressGesture(minimumDuration: 1.0, maximumDistance: 10)
    .onEnded { _ in triggerHaptic() }

With visual feedback via `@GestureState` + `.updating()`:

@GestureState private var isPressing = false

Circle()
    .fill(isPressing ? .red : .blue)
    .scaleEffect(isPressing ? 1.2 : 1.0)
    .gesture(
        LongPressGesture(minimumDuration: 0.8)
            .updating($isPressing) { current, state, _ in state = current }
            .onEnded { _ in completedLongPress = true }
    )

Shorthand: `.onLongPressGesture(minimumDuration:perform:onPressingChanged:)`.

DragGesture

Tracks finger movement. `Value` provides `startLocation`, `location`, `translation`, `velocity`, and `predictedEndTranslation`. `DragGesture.Value.velocity` is available with `DragGesture` from iOS 13+; do not confuse it with iOS 17+ gesture types such as `MagnifyGesture` and `RotateGesture`.

@State private var offset = CGSize.zero

RoundedRectangle(cornerRadius: 16)
    .fill(.blue)
    .frame(width: 100, height: 100)
    .offset(offset)
    .gesture(
        DragGesture()
            .onChanged { value in offset = value.translation }
            .onEnded { _ in withAnimation(.spring) { offset = .zero } }
    )

Configure minimum distance and coordinate space:

DragGesture(minimumDistance: 20, coordinateSpace: .global)

MagnifyGesture (iOS 17+)

Replaces the deprecated `MagnificationGesture`. Tracks pinch-to-zoom scale.

@GestureState private var magnifyBy = 1.0

Image("photo")
    .resizable().scaledToFit()
    .scaleEffect(magnifyBy)
    .gesture(
        MagnifyGesture()
            .updating($magnifyBy) { value, state, _ in
                state = value.magnification
            }
    )

RotateGesture (iOS 17+)

`RotateGesture` is the newer alternative to `RotationGesture`. Tracks two-finger rotation angle.

@State private var angle = Angle.zero

Rectangle()
    .fill(.blue).frame(width: 200, height: 200)
    .rotationEffect(angle)
    .gesture(
        RotateGesture(minimumAngleDelta: .degrees(1))
            .onChanged { value in angle = value.rotation }
    )

For persisted, clamped magnification and combined rotation examples, load [references/gesture-patterns.md](references/gesture-patterns.md).

Gesture Composition

`.simultaneously(with:)` — both gestures recognized at the same time

let magnify = MagnifyGesture()
    .onChanged { value in scale = value.magnification }

let rotate = RotateGesture()
    .onChanged { value in angle = value.rotation }

Image("photo")
    .scaleEffect(scale)
    .rotationEffect(angle)
    .gesture(magnify.simultaneously(with: rotate))

The value is `SimultaneousGesture.Value` with `.first` and `.second` optionals.

`.sequenced(before:)` — first must succeed before second begins

let longPressBeforeDrag = LongPressGesture(minimumDuration: 0.5)
    .sequenced(before: DragGesture())
    .onEnded { value in
        guard case .seco
Read more
@dpearson2699
Ships withswift-ios-skills

86 agent skills optimized for iOS 26+ development with Swift 6.3 and modern Apple frameworks.

1.3k86 skills1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
1.3k
Star history of dpearson2699/swift-ios-skills on GitHub, a chart by star-history.com

Other skills on swift-ios-skills.

alarmkit

alarmkit

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple…

app-clips

app-clips

Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store…

More skills from other plugins.

deep-code-review

deep-code-review

In-depth design-focused code review - understands codebase context before evaluating PR changes, posts structured feedback to…

expo1d53k
@expo@expoInstall Skill
modlens

modlens

Plug-in vision for text-only models. Hard rule: when a file path or URL with an image extension (.png, .jpg, .jpeg, .webp, .gif,…

modlens2w4.6k
@liustack@liustackInstall Skill
appllama-app-design-skill

appllama-app-design-sk…

Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile…

appllama2mo3.9k
@appllama@appllamaInstall Skill

Questions about swiftui-gestures.

Which coding agents can use swiftui-gestures?
swiftui-gestures installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.