{"id":"5bab604e-88b2-46d3-b88e-d79dced66b75","shortId":"LYvpUG","kind":"skill","title":"swiftui-gestures","tagline":"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 hig","description":"# SwiftUI Gestures (iOS 26+)\n\nReview, write, and fix SwiftUI gesture interactions. Apply modern gesture APIs\nwith correct composition, state management, and conflict resolution using\nSwift 6.3 patterns.\n\n## Contents\n\n- [Gesture Overview](#gesture-overview)\n- [TapGesture](#tapgesture)\n- [LongPressGesture](#longpressgesture)\n- [DragGesture](#draggesture)\n- [MagnifyGesture (iOS 17+)](#magnifygesture-ios-17)\n- [RotateGesture (iOS 17+)](#rotategesture-ios-17)\n- [Gesture Composition](#gesture-composition)\n- [@GestureState](#gesturestate)\n- [Adding Gestures to Views](#adding-gestures-to-views)\n- [Custom Gesture Protocol](#custom-gesture-protocol)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Gesture Overview\n\n| Gesture | Type | Value | Since |\n|---|---|---|---|\n| `TapGesture` | Discrete | `Void` | iOS 13 |\n| `LongPressGesture` | Discrete | `Bool` | iOS 13 |\n| `DragGesture` | Continuous | `DragGesture.Value` | iOS 13 |\n| `MagnifyGesture` | Continuous | `MagnifyGesture.Value` | iOS 17 |\n| `RotateGesture` | Continuous | `RotateGesture.Value` | iOS 17 |\n| `SpatialTapGesture` | Discrete | `SpatialTapGesture.Value` | iOS 16 |\n\n**Discrete** gestures fire once (`.onEnded`). **Continuous** gestures stream\nupdates (`.onChanged`, `.onEnded`, `.updating`).\n\n## TapGesture\n\nRecognizes one or more taps. Use the `count` parameter for multi-tap.\n\n```swift\n// Single, double, and triple tap\nTapGesture()            .onEnded { tapped.toggle() }\nTapGesture(count: 2)    .onEnded { handleDoubleTap() }\nTapGesture(count: 3)    .onEnded { handleTripleTap() }\n\n// Shorthand modifier\nText(\"Tap me\").onTapGesture(count: 2) { handleDoubleTap() }\n```\n\n## LongPressGesture\n\nSucceeds after the user holds for `minimumDuration`. Fails if finger moves\nbeyond `maximumDistance`.\n\n```swift\n// Basic long press (0.5s default)\nLongPressGesture()\n    .onEnded { _ in showMenu = true }\n\n// Custom duration and distance tolerance\nLongPressGesture(minimumDuration: 1.0, maximumDistance: 10)\n    .onEnded { _ in triggerHaptic() }\n```\n\nWith visual feedback via `@GestureState` + `.updating()`:\n\n```swift\n@GestureState private var isPressing = false\n\nCircle()\n    .fill(isPressing ? .red : .blue)\n    .scaleEffect(isPressing ? 1.2 : 1.0)\n    .gesture(\n        LongPressGesture(minimumDuration: 0.8)\n            .updating($isPressing) { current, state, _ in state = current }\n            .onEnded { _ in completedLongPress = true }\n    )\n```\n\nShorthand: `.onLongPressGesture(minimumDuration:perform:onPressingChanged:)`.\n\n## DragGesture\n\nTracks finger movement. `Value` provides `startLocation`, `location`,\n`translation`, `velocity`, and `predictedEndTranslation`.\n\n```swift\n@State private var offset = CGSize.zero\n\nRoundedRectangle(cornerRadius: 16)\n    .fill(.blue)\n    .frame(width: 100, height: 100)\n    .offset(offset)\n    .gesture(\n        DragGesture()\n            .onChanged { value in offset = value.translation }\n            .onEnded { _ in withAnimation(.spring) { offset = .zero } }\n    )\n```\n\nConfigure minimum distance and coordinate space:\n\n```swift\nDragGesture(minimumDistance: 20, coordinateSpace: .global)\n```\n\n## MagnifyGesture (iOS 17+)\n\nReplaces the deprecated `MagnificationGesture`. Tracks pinch-to-zoom scale.\n\n```swift\n@GestureState private var magnifyBy = 1.0\n\nImage(\"photo\")\n    .resizable().scaledToFit()\n    .scaleEffect(magnifyBy)\n    .gesture(\n        MagnifyGesture()\n            .updating($magnifyBy) { value, state, _ in\n                state = value.magnification\n            }\n    )\n```\n\nWith persisted scale:\n\n```swift\n@State private var currentScale = 1.0\n@GestureState private var gestureScale = 1.0\n\nImage(\"photo\")\n    .scaleEffect(currentScale * gestureScale)\n    .gesture(\n        MagnifyGesture(minimumScaleDelta: 0.01)\n            .updating($gestureScale) { value, state, _ in state = value.magnification }\n            .onEnded { value in\n                currentScale = min(max(currentScale * value.magnification, 0.5), 5.0)\n            }\n    )\n```\n\n## RotateGesture (iOS 17+)\n\n`RotateGesture` is the newer alternative to `RotationGesture`. Tracks two-finger rotation angle.\n\n```swift\n@State private var angle = Angle.zero\n\nRectangle()\n    .fill(.blue).frame(width: 200, height: 200)\n    .rotationEffect(angle)\n    .gesture(\n        RotateGesture(minimumAngleDelta: .degrees(1))\n            .onChanged { value in angle = value.rotation }\n    )\n```\n\nWith persisted rotation:\n\n```swift\n@State private var currentAngle = Angle.zero\n@GestureState private var gestureAngle = Angle.zero\n\nRectangle()\n    .rotationEffect(currentAngle + gestureAngle)\n    .gesture(\n        RotateGesture()\n            .updating($gestureAngle) { value, state, _ in state = value.rotation }\n            .onEnded { value in currentAngle += value.rotation }\n    )\n```\n\n## Gesture Composition\n\n### `.simultaneously(with:)` — both gestures recognized at the same time\n\n```swift\nlet magnify = MagnifyGesture()\n    .onChanged { value in scale = value.magnification }\n\nlet rotate = RotateGesture()\n    .onChanged { value in angle = value.rotation }\n\nImage(\"photo\")\n    .scaleEffect(scale)\n    .rotationEffect(angle)\n    .gesture(magnify.simultaneously(with: rotate))\n```\n\nThe value is `SimultaneousGesture.Value` with `.first` and `.second` optionals.\n\n### `.sequenced(before:)` — first must succeed before second begins\n\n```swift\nlet longPressBeforeDrag = LongPressGesture(minimumDuration: 0.5)\n    .sequenced(before: DragGesture())\n    .onEnded { value in\n        guard case .second(true, let drag?) = value else { return }\n        finalOffset.width += drag.translation.width\n        finalOffset.height += drag.translation.height\n    }\n```\n\n### `.exclusively(before:)` — only one succeeds (first has priority)\n\n```swift\nlet doubleTapOrLongPress = TapGesture(count: 2)\n    .map { ExclusiveResult.doubleTap }\n    .exclusively(before:\n        LongPressGesture()\n            .map { _ in ExclusiveResult.longPress }\n    )\n    .onEnded { result in\n        switch result {\n        case .first(let val): handleDoubleTap()\n        case .second(let val): handleLongPress()\n        }\n    }\n```\n\n## @GestureState\n\n`@GestureState` is a property wrapper that **automatically resets** to its\ninitial value when the gesture ends. Use for transient feedback; use `@State`\nfor values that persist.\n\n```swift\n@GestureState private var dragOffset = CGSize.zero  // resets to .zero\n@State private var position = CGSize.zero            // persists\n\nCircle()\n    .offset(\n        x: position.width + dragOffset.width,\n        y: position.height + dragOffset.height\n    )\n    .gesture(\n        DragGesture()\n            .updating($dragOffset) { value, state, _ in\n                state = value.translation\n            }\n            .onEnded { value in\n                position.width += value.translation.width\n                position.height += value.translation.height\n            }\n    )\n```\n\nCustom reset with animation: `@GestureState(resetTransaction: Transaction(animation: .spring))`\n\n## Adding Gestures to Views\n\nThree modifiers control gesture priority in the view hierarchy:\n\n| Modifier | Behavior |\n|---|---|\n| `.gesture()` | Default priority. Child gestures win over parent. |\n| `.highPriorityGesture()` | Parent gesture takes precedence over child. |\n| `.simultaneousGesture()` | Both parent and child gestures fire. |\n\n```swift\n// Problem: parent tap swallows child tap\nVStack {\n    Button(\"Child\") { handleChild() }  // never fires\n}\n.gesture(TapGesture().onEnded { handleParent() })\n\n// Fix 1: Use simultaneousGesture on parent\nVStack {\n    Button(\"Child\") { handleChild() }\n}\n.simultaneousGesture(TapGesture().onEnded { handleParent() })\n\n// Fix 2: Give parent explicit priority\nVStack {\n    Text(\"Child\")\n        .gesture(TapGesture().onEnded { handleChild() })\n}\n.highPriorityGesture(TapGesture().onEnded { handleParent() })\n```\n\n### GestureMask\n\nControl which gestures participate when using `.gesture(_:including:)`:\n\n```swift\n.gesture(drag, including: .gesture)   // only this gesture, not subviews\n.gesture(drag, including: .subviews)  // only subview gestures\n.gesture(drag, including: .all)       // default: this + subviews\n```\n\n## Custom Gesture Protocol\n\nCreate reusable gestures by conforming to `Gesture`:\n\n```swift\nstruct SwipeGesture: Gesture {\n    enum Direction { case left, right, up, down }\n    let minimumDistance: CGFloat\n    let onSwipe: (Direction) -> Void\n\n    init(minimumDistance: CGFloat = 50, onSwipe: @escaping (Direction) -> Void) {\n        self.minimumDistance = minimumDistance\n        self.onSwipe = onSwipe\n    }\n\n    var body: some Gesture {\n        DragGesture(minimumDistance: minimumDistance)\n            .onEnded { value in\n                let h = value.translation.width, v = value.translation.height\n                if abs(h) > abs(v) {\n                    onSwipe(h > 0 ? .right : .left)\n                } else {\n                    onSwipe(v > 0 ? .down : .up)\n                }\n            }\n    }\n}\n\n// Usage\nRectangle().gesture(SwipeGesture { print(\"Swiped \\($0)\") })\n```\n\nWrap in a `View` extension for ergonomic API:\n\n```swift\nextension View {\n    func onSwipe(perform action: @escaping (SwipeGesture.Direction) -> Void) -> some View {\n        gesture(SwipeGesture(onSwipe: action))\n    }\n}\n```\n\n## Common Mistakes\n\n### 1. Conflicting parent/child gestures\n\n```swift\n// DON'T: Parent .gesture() conflicts with child tap\nVStack {\n    Button(\"Action\") { doSomething() }\n}\n.gesture(TapGesture().onEnded { parentAction() })\n\n// DO: Use .simultaneousGesture() or .highPriorityGesture()\nVStack {\n    Button(\"Action\") { doSomething() }\n}\n.simultaneousGesture(TapGesture().onEnded { parentAction() })\n```\n\n### 2. Using @State instead of @GestureState for transient state\n\n```swift\n// DON'T: @State doesn't auto-reset — view stays offset after gesture ends\n@State private var dragOffset = CGSize.zero\n\nDragGesture()\n    .onChanged { value in dragOffset = value.translation }\n    .onEnded { _ in dragOffset = .zero }  // manual reset required\n\n// DO: @GestureState auto-resets when gesture ends\n@GestureState private var dragOffset = CGSize.zero\n\nDragGesture()\n    .updating($dragOffset) { value, state, _ in\n        state = value.translation\n    }\n```\n\n### 3. Not using .updating() for intermediate feedback\n\n```swift\n// DON'T: No visual feedback during long press\nLongPressGesture(minimumDuration: 2.0)\n    .onEnded { _ in showResult = true }\n\n// DO: Provide feedback while pressing\n@GestureState private var isPressing = false\n\nLongPressGesture(minimumDuration: 2.0)\n    .updating($isPressing) { current, state, _ in\n        state = current\n    }\n    .onEnded { _ in showResult = true }\n```\n\n### 4. Using deprecated gesture types on iOS 17+\n\n```swift\n// DON'T: Deprecated since iOS 17\nMagnificationGesture()   // deprecated — use MagnifyGesture()\n\n// DO: Use newer gesture types\nMagnifyGesture()         // iOS 17+\nRotateGesture()          // iOS 17+ (newer alternative to RotationGesture)\n```\n\n### 5. Heavy computation in onChanged\n\n```swift\n// DON'T: Expensive work called every frame (~60-120 Hz)\nDragGesture()\n    .onChanged { value in\n        let result = performExpensiveHitTest(at: value.location)\n        let filtered = applyComplexFilter(result)\n        updateModel(filtered)\n    }\n\n// DO: Throttle or defer expensive work\nDragGesture()\n    .onChanged { value in\n        dragPosition = value.location  // lightweight state update only\n    }\n    .onEnded { value in\n        performExpensiveHitTest(at: value.location)  // once at end\n    }\n```\n\n### 6. Using onTapGesture for actions that should be a Button\n\n```swift\n// DON'T: onTapGesture has no accessibility traits, VoiceOver role,\n// Voice Control targeting, Switch Control scanning, or keyboard activation\nText(\"Delete\")\n    .onTapGesture { deleteItem() }\n\n// DO: Button provides all of these automatically\nButton(\"Delete\", role: .destructive) { deleteItem() }\n\n// DO: For custom visuals, use ButtonStyle instead of onTapGesture\nButton { toggleExpanded() } label: {\n    CardView()\n}\n.buttonStyle(.plain)\n```\n\nReserve `onTapGesture` for multi-tap (`count: 2+`), tap-location-dependent\nbehavior, or adding tap recognition to non-interactive content that already\nhas appropriate accessibility traits.\n\n## Review Checklist\n\n- [ ] Correct gesture type: `MagnifyGesture`/`RotateGesture` (not deprecated `Magnification`/`Rotation` variants)\n- [ ] `@GestureState` used for transient values that should reset; `@State` for persisted values\n- [ ] `.updating()` provides intermediate visual feedback during continuous gestures\n- [ ] Parent/child conflicts resolved with `.highPriorityGesture()` or `.simultaneousGesture()`\n- [ ] `onChanged` closures are lightweight — no heavy computation every frame\n- [ ] Composed gestures use correct combinator: `simultaneously`, `sequenced`, or `exclusively`\n- [ ] Persisted scale/rotation clamped to reasonable bounds in `onEnded`\n- [ ] Custom `Gesture` conformances use `var body: some Gesture` (not `View`)\n- [ ] Gesture-driven animations use `.spring` or similar for natural deceleration\n- [ ] `GestureMask` considered when mixing gestures across view hierarchy levels\n- [ ] `onTapGesture` only used where `count > 1`, tap location, or coordinate space matters — plain single-tap actions use `Button` instead\n\n## References\n\n- See [references/gesture-patterns.md](references/gesture-patterns.md) for drag-to-reorder, pinch-to-zoom, combined rotate+scale, velocity calculations, and SwiftUI/UIKit gesture interop.\n- [Gesture protocol](https://sosumi.ai/documentation/swiftui/gesture)\n- [TapGesture](https://sosumi.ai/documentation/swiftui/tapgesture)\n- [LongPressGesture](https://sosumi.ai/documentation/swiftui/longpressgesture)\n- [DragGesture](https://sosumi.ai/documentation/swiftui/draggesture)\n- [MagnifyGesture](https://sosumi.ai/documentation/swiftui/magnifygesture)\n- [RotateGesture](https://sosumi.ai/documentation/swiftui/rotategesture)\n- [GestureState](https://sosumi.ai/documentation/swiftui/gesturestate)\n- [Composing SwiftUI gestures](https://sosumi.ai/documentation/swiftui/composing-swiftui-gestures)\n- [Adding interactivity with gestures](https://sosumi.ai/documentation/swiftui/adding-interactivity-with-gestures)","tags":["swiftui","gestures","swift","ios","skills","dpearson2699","accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills"],"capabilities":["skill","source-dpearson2699","skill-swiftui-gestures","topic-accessibility","topic-agent-skills","topic-ai-coding","topic-apple","topic-claude-code","topic-codex-skills","topic-cursor-skills","topic-ios","topic-ios-development","topic-liquid-glass","topic-localization","topic-mapkit"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-gestures","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dpearson2699/swift-ios-skills","source_repo":"https://github.com/dpearson2699/swift-ios-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 599 github stars · SKILL.md body (14,206 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:45.601Z","embedding":null,"createdAt":"2026-04-18T20:33:57.028Z","updatedAt":"2026-05-18T18:53:45.601Z","lastSeenAt":"2026-05-18T18:53:45.601Z","tsv":"'-120':1104 '/documentation/swiftui/adding-interactivity-with-gestures)':1412 '/documentation/swiftui/composing-swiftui-gestures)':1405 '/documentation/swiftui/draggesture)':1387 '/documentation/swiftui/gesture)':1375 '/documentation/swiftui/gesturestate)':1399 '/documentation/swiftui/longpressgesture)':1383 '/documentation/swiftui/magnifygesture)':1391 '/documentation/swiftui/rotategesture)':1395 '/documentation/swiftui/tapgesture)':1379 '0':870,876,885 '0.01':406 '0.5':233,422,558 '0.8':278 '1':460,745,912,1334 '1.0':248,274,368,392,397 '1.2':273 '10':250 '100':320,322 '13':135,140,145 '16':160,315 '17':78,82,85,89,150,155,352,426,1063,1070,1082,1085 '2':198,213,591,759,946,1213 '2.0':1027,1044 '20':347 '200':451,453 '26':40 '3':203,1009 '4':1056 '5':1090 '5.0':423 '50':839 '6':1146 '6.3':62 '60':1103 'ab':864,866 'access':1162,1232 'across':1325 'action':900,909,927,940,1150,1345 'activ':1174 'ad':13,97,102,690,1220,1406 'adding-gestures-to-view':101 'alreadi':1229 'altern':431,1087 'angl':439,444,455,464,524,531 'angle.zero':445,474,479 'anim':684,688,1312 'api':51,893 'appli':48 'applycomplexfilt':1117 'appropri':1231 'auto':962,991 'auto-reset':961,990 'automat':622,1185 'basic':230 'begin':552 'behavior':704,1218 'beyond':227 'blue':270,317,448 'bodi':849,1304 'bool':138 'bound':1296 'button':735,751,926,939,1155,1180,1186,1200,1347 'buttonstyl':1196,1204 'calcul':1366 'call':1100 'cardview':1203 'case':566,605,610,824 'cgfloat':831,838 'cgsize.zero':312,647,655,974,1000 'checklist':119,122,1235 'child':708,719,724,732,736,752,766,923 'circl':266,657 'clamp':1293 'closur':1274 'combin':1286,1362 'common':113,116,910 'common-mistak':115 'completedlongpress':288 'compos':22,1282,1400 'composit':54,91,94,499 'comput':1092,1279 'configur':338 'conflict':34,58,913,921,1267 'conform':815,1301 'consid':1321 'content':64,1227 'continu':142,147,152,166,1264 'control':696,776,1167,1170 'coordin':342,1338 'coordinatespac':348 'cornerradius':314 'correct':53,1236,1285 'count':181,197,202,212,590,1212,1333 'creat':811 'current':281,285,1047,1051 'currentangl':473,482,496 'currentscal':391,401,417,420 'custom':106,110,241,681,808,1193,1299 'custom-gesture-protocol':109 'deceler':1319 'default':235,706,805 'defer':1124 'degre':459 'delet':1176,1187 'deleteitem':1178,1190 'depend':1217 'deprec':355,1058,1067,1072,1242 'destruct':1189 'direct':823,834,842 'discret':132,137,157,161 'distanc':244,340 'doesn':959 'dosometh':928,941 'doubl':189 'doubletaporlongpress':588 'drag':17,570,786,795,802,1355 'drag-to-reord':1354 'drag.translation.height':577 'drag.translation.width':575 'draggestur':74,75,141,295,326,345,561,666,852,975,1001,1106,1127,1384 'draggesture.value':143 'dragoffset':646,668,973,979,983,999,1003 'dragoffset.height':664 'dragoffset.width':661 'dragposit':1131 'driven':1311 'durat':242 'els':572,873 'end':631,969,995,1145 'enum':822 'ergonom':892 'escap':841,901 'everi':1101,1280 'exclus':578,594,1290 'exclusiveresult.doubletap':593 'exclusiveresult.longpress':599 'expens':1098,1125 'explicit':762 'extens':890,895 'fail':223 'fals':265,1041 'feedback':256,635,1015,1021,1034,1262 'fill':267,316,447 'filter':1116,1120 'finaloffset.height':576 'finaloffset.width':574 'finger':225,297,437 'fire':163,726,739 'first':541,547,583,606 'fix':44,744,758 'frame':318,449,1102,1281 'func':897 'gestur':3,9,21,23,33,38,46,50,65,68,90,93,98,103,107,111,125,127,162,167,275,325,375,403,456,484,498,503,532,630,665,691,697,705,709,715,725,740,767,778,782,785,788,791,794,800,801,809,813,817,821,851,881,906,915,920,929,968,994,1059,1078,1237,1265,1283,1300,1306,1310,1324,1369,1371,1402,1409 'gesture-composit':92 'gesture-driven':1309 'gesture-overview':67 'gestureangl':478,483,487 'gesturemask':775,1320 'gesturescal':396,402,408 'gesturest':30,95,96,258,261,364,393,475,615,616,643,685,951,989,996,1037,1246,1396 'give':760 'global':349 'guard':565 'h':859,865,869 'handl':10 'handlechild':737,753,770 'handledoubletap':200,214,609 'handlelongpress':614 'handlepar':743,757,774 'handletripletap':205 'heavi':1091,1278 'height':321,452 'hierarchi':702,1327 'hig':36 'highprioritygestur':713,771,937,1270 'hold':220 'hz':1105 'imag':369,398,526 'implement':4 'improv':7 'includ':783,787,796,803 'init':836 'initi':626 'instead':949,1197,1348 'interact':47,1226,1407 'intermedi':1014,1260 'interop':1370 'io':39,77,81,84,88,134,139,144,149,154,159,351,425,1062,1069,1081,1084 'ispress':264,268,272,280,1040,1046 'keyboard':1173 'label':1202 'left':825,872 'let':510,518,554,569,587,607,612,829,832,858,1110,1115 'level':1328 'lightweight':1133,1276 'locat':302,1216,1336 'long':15,231,1023 'longpressbeforedrag':555 'longpressgestur':72,73,136,215,236,246,276,556,596,1025,1042,1380 'magnif':1243 'magnifi':18,511 'magnificationgestur':356,1071 'magnify.simultaneously':533 'magnifybi':367,374,378 'magnifygestur':76,80,146,350,376,404,512,1074,1080,1239,1388 'magnifygesture-io':79 'magnifygesture.value':148 'manag':26,56 'manual':985 'map':592,597 'matter':1340 'max':419 'maximumdist':228,249 'min':418 'minimum':339 'minimumangledelta':458 'minimumdist':346,830,837,845,853,854 'minimumdur':222,247,277,292,557,1026,1043 'minimumscaledelta':405 'mistak':114,117,911 'mix':1323 'modern':49 'modifi':207,695,703 'move':226 'movement':298 'multi':185,1210 'multi-tap':184,1209 'must':548 'natur':1318 'never':738 'newer':430,1077,1086 'non':1225 'non-interact':1224 'offset':311,323,324,330,336,658,966 'onchang':170,327,461,513,521,976,1094,1107,1128,1273 'one':175,581 'onend':165,171,194,199,204,237,251,286,332,414,493,562,600,674,742,756,769,773,855,931,944,981,1028,1052,1137,1298 'onlongpressgestur':291 'onpressingchang':294 'onswip':833,840,847,868,874,898,908 'ontapgestur':211,1148,1159,1177,1199,1207,1329 'option':544 'overview':66,69,126 'paramet':182 'parent':712,714,722,729,749,761,919 'parent/child':32,914,1266 'parentact':932,945 'particip':779 'pattern':63 'perform':293,899 'performexpensivehittest':1112,1140 'persist':385,467,641,656,1256,1291 'photo':370,399,527 'pinch':359,1359 'pinch-to-zoom':358,1358 'plain':1205,1341 'posit':654 'position.height':663,679 'position.width':660,677 'preced':717 'predictedendtransl':306 'press':16,232,1024,1036 'print':883 'prioriti':585,698,707,763 'privat':262,309,365,389,394,442,471,476,644,652,971,997,1038 'problem':728 'properti':619 'protocol':108,112,810,1372 'provid':300,1033,1181,1259 'reason':1295 'recogn':174,504 'recognit':1222 'rectangl':446,480,880 'red':269 'refer':123,124,1349 'references/gesture-patterns.md':1351,1352 'reorder':1357 'replac':353 'requir':987 'reserv':1206 'reset':623,648,682,963,986,992,1253 'resettransact':686 'resiz':371 'resolut':59 'resolv':31,1268 'result':601,604,1111,1118 'return':573 'reusabl':812 'review':5,41,118,121,1234 'review-checklist':120 'right':826,871 'role':1165,1188 'rotat':20,438,468,519,535,1244,1363 'rotategestur':83,87,151,424,427,457,485,520,1083,1240,1392 'rotategesture-io':86 'rotategesture.value':153 'rotationeffect':454,481,530 'rotationgestur':433,1089 'roundedrectangl':313 'scale':362,386,516,529,1364 'scale/rotation':1292 'scaledtofit':372 'scaleeffect':271,373,400,528 'scan':1171 'second':543,551,567,611 'see':1350 'self.minimumdistance':844 'self.onswipe':846 'sequenc':545,559,1288 'shorthand':206,290 'showmenu':239 'showresult':1030,1054 'similar':1316 'simultan':500,1287 'simultaneousgestur':720,747,754,935,942,1272 'simultaneousgesture.value':539 'simultaneously/sequenced/exclusively':25 'sinc':130,1068 'singl':188,1343 'single-tap':1342 'skill' 'skill-swiftui-gestures' 'sosumi.ai':1374,1378,1382,1386,1390,1394,1398,1404,1411 'sosumi.ai/documentation/swiftui/adding-interactivity-with-gestures)':1410 'sosumi.ai/documentation/swiftui/composing-swiftui-gestures)':1403 'sosumi.ai/documentation/swiftui/draggesture)':1385 'sosumi.ai/documentation/swiftui/gesture)':1373 'sosumi.ai/documentation/swiftui/gesturestate)':1397 'sosumi.ai/documentation/swiftui/longpressgesture)':1381 'sosumi.ai/documentation/swiftui/magnifygesture)':1389 'sosumi.ai/documentation/swiftui/rotategesture)':1393 'sosumi.ai/documentation/swiftui/tapgesture)':1377 'source-dpearson2699' 'space':343,1339 'spatialtapgestur':156 'spatialtapgesture.value':158 'spring':335,689,1314 'startloc':301 'state':28,55,282,284,308,380,382,388,410,412,441,470,489,491,637,651,670,672,948,954,958,970,1005,1007,1048,1050,1134,1254 'stay':965 'stream':168 'struct':819 'subview':793,797,799,807 'succeed':216,549,582 'swallow':731 'swift':61,187,229,260,307,344,363,387,440,469,509,553,586,642,727,784,818,894,916,955,1016,1064,1095,1156 'swiftui':2,8,37,45,1401 'swiftui-gestur':1 'swiftui/uikit':1368 'swipe':884 'swipegestur':820,882,907 'swipegesture.direction':902 'switch':603,1169 'take':716 'tap':14,178,186,192,209,730,733,924,1211,1215,1221,1335,1344 'tap-location-depend':1214 'tapgestur':70,71,131,173,193,196,201,589,741,755,768,772,930,943,1376 'tapped.toggle':195 'target':1168 'text':208,765,1175 'three':694 'throttl':1122 'time':508 'toggleexpand':1201 'toler':245 'topic-accessibility' 'topic-agent-skills' 'topic-ai-coding' 'topic-apple' 'topic-claude-code' 'topic-codex-skills' 'topic-cursor-skills' 'topic-ios' 'topic-ios-development' 'topic-liquid-glass' 'topic-localization' 'topic-mapkit' 'track':296,357,434 'trait':1163,1233 'transact':687 'transient':27,634,953,1249 'translat':303 'triggerhapt':253 'tripl':191 'true':240,289,568,1031,1055 'two':436 'two-fing':435 'type':128,1060,1079,1238 'updat':169,172,259,279,377,407,486,667,1002,1012,1045,1135,1258 'updatemodel':1119 'usag':879 'use':11,60,179,632,636,746,781,934,947,1011,1057,1073,1076,1147,1195,1247,1284,1302,1313,1331,1346 'user':219 'v':861,867,875 'val':608,613 'valu':129,299,328,379,409,415,462,488,494,514,522,537,563,571,627,639,669,675,856,977,1004,1108,1129,1138,1250,1257 'value.location':1114,1132,1142 'value.magnification':383,413,421,517 'value.rotation':465,492,497,525 'value.translation':331,673,980,1008 'value.translation.height':680,862 'value.translation.width':678,860 'var':263,310,366,390,395,443,472,477,645,653,848,972,998,1039,1303 'variant':1245 'veloc':304,1365 'via':257 'view':100,105,693,701,889,896,905,964,1308,1326 'visual':255,1020,1194,1261 'voic':1166 'voiceov':1164 'void':133,835,843,903 'vstack':734,750,764,925,938 'width':319,450 'win':710 'withanim':334 'work':1099,1126 'wrap':886 'wrapper':620 'write':42 'x':659 'y':662 'zero':337,650,984 'zoom':361,1361","prices":[{"id":"0349273f-4309-40b5-8ce8-c6907d96cd71","listingId":"5bab604e-88b2-46d3-b88e-d79dced66b75","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:33:57.028Z"}],"sources":[{"listingId":"5bab604e-88b2-46d3-b88e-d79dced66b75","source":"github","sourceId":"dpearson2699/swift-ios-skills/swiftui-gestures","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-gestures","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:23.986Z","lastSeenAt":"2026-05-18T18:53:45.601Z"},{"listingId":"5bab604e-88b2-46d3-b88e-d79dced66b75","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/swiftui-gestures","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-gestures","isPrimary":true,"firstSeenAt":"2026-04-18T20:33:57.028Z","lastSeenAt":"2026-05-07T22:40:33.171Z"}],"details":{"listingId":"5bab604e-88b2-46d3-b88e-d79dced66b75","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"swiftui-gestures","github":{"repo":"dpearson2699/swift-ios-skills","stars":599,"topics":["accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills","ios","ios-development","liquid-glass","localization","mapkit","networking","storekit","swift","swift-concurrency","swiftdata","swiftui","widgetkit","xcode"],"license":"other","html_url":"https://github.com/dpearson2699/swift-ios-skills","pushed_at":"2026-04-26T21:04:17Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"1325e9a37c0e63a2dee4e2b874af238d55e0a22b","skill_md_path":"skills/swiftui-gestures/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-gestures"},"layout":"multi","source":"github","category":"swift-ios-skills","frontmatter":{"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."},"skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-gestures"},"updatedAt":"2026-05-18T18:53:45.601Z"}}