{"id":"5bab604e-88b2-46d3-b88e-d79dced66b75","shortId":"LYvpUG","kind":"skill","title":"Swiftui Gestures","tagline":"Swift Ios Skills skill by Dpearson2699","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// PREFER: 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## 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\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"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"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":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","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:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T05:40:39.293Z","embedding":null,"createdAt":"2026-04-18T20:33:57.028Z","updatedAt":"2026-04-22T05:40:39.293Z","lastSeenAt":"2026-04-22T05:40:39.293Z","tsv":"'-120':1075 '/documentation/swiftui/adding-interactivity-with-gestures)':1275 '/documentation/swiftui/composing-swiftui-gestures)':1268 '/documentation/swiftui/draggesture)':1250 '/documentation/swiftui/gesture)':1238 '/documentation/swiftui/gesturestate)':1262 '/documentation/swiftui/longpressgesture)':1246 '/documentation/swiftui/magnifygesture)':1254 '/documentation/swiftui/rotategesture)':1258 '/documentation/swiftui/tapgesture)':1242 '0':842,848,857 '0.01':378 '0.5':205,394,530 '0.8':250 '1':432,717,884 '1.0':220,246,340,364,369 '1.2':245 '10':222 '100':292,294 '13':107,112,117 '16':132,287 '17':50,54,57,61,122,127,324,398,1035,1042,1053,1056 '2':170,185,563,731,918 '2.0':999,1016 '20':319 '200':423,425 '26':12 '3':175,981 '4':1028 '5':1061 '5.0':395 '50':811 '6.3':34 '60':1074 'ab':836,838 'across':1208 'action':872,881,899,912 'ad':69,74,662,1269 'adding-gestures-to-view':73 'altern':403,1058 'angl':411,416,427,436,496,503 'angle.zero':417,446,451 'anim':656,660,1195 'api':23,865 'appli':20 'applycomplexfilt':1088 'auto':934,963 'auto-reset':933,962 'automat':594 'basic':202 'begin':524 'behavior':676 'beyond':199 'blue':242,289,420 'bodi':821,1187 'bool':110 'bound':1179 'button':707,723,898,911 'calcul':1229 'call':1071 'case':538,577,582,796 'category-swift-ios-skills' 'cgfloat':803,810 'cgsize.zero':284,619,627,946,972 'checklist':91,94,1118 'child':680,691,696,704,708,724,738,895 'circl':238,629 'clamp':1176 'closur':1157 'combin':1169,1225 'common':85,88,882 'common-mistak':87 'completedlongpress':260 'compos':1165,1263 'composit':26,63,66,471 'comput':1063,1162 'configur':310 'conflict':30,885,893,1150 'conform':787,1184 'consid':1204 'content':36 'continu':114,119,124,138,1147 'control':668,748 'coordin':314 'coordinatespac':320 'cornerradius':286 'correct':25,1119,1168 'count':153,169,174,184,562 'creat':783 'current':253,257,1019,1023 'currentangl':445,454,468 'currentscal':363,373,389,392 'custom':78,82,213,653,780,1182 'custom-gesture-protocol':81 'deceler':1202 'default':207,678,777 'defer':1095 'degre':431 'deprec':327,1030,1039,1044,1125 'direct':795,806,814 'discret':104,109,129,133 'distanc':216,312 'doesn':931 'dosometh':900,913 'doubl':161 'doubletaporlongpress':560 'dpearson2699':8 'drag':542,758,767,774,1218 'drag-to-reord':1217 'drag.translation.height':549 'drag.translation.width':547 'draggestur':46,47,113,267,298,317,533,638,824,947,973,1077,1098,1247 'draggesture.value':115 'dragoffset':618,640,945,951,955,971,975 'dragoffset.height':636 'dragoffset.width':633 'dragposit':1102 'driven':1194 'durat':214 'els':544,845 'end':603,941,967,1116 'enum':794 'ergonom':864 'escap':813,873 'everi':1072,1163 'exclus':550,566,1173 'exclusiveresult.doubletap':565 'exclusiveresult.longpress':571 'expens':1069,1096 'explicit':734 'extens':862,867 'fail':195 'fals':237,1013 'feedback':228,607,987,993,1006,1145 'fill':239,288,419 'filter':1087,1091 'finaloffset.height':548 'finaloffset.width':546 'finger':197,269,409 'fire':135,698,711 'first':513,519,555,578 'fix':16,716,730 'frame':290,421,1073,1164 'func':869 'gestur':2,10,18,22,37,40,62,65,70,75,79,83,97,99,134,139,247,297,347,375,428,456,470,475,504,602,637,663,669,677,681,687,697,712,739,750,754,757,760,763,766,772,773,781,785,789,793,823,853,878,887,892,901,940,966,1031,1049,1120,1148,1166,1183,1189,1193,1207,1232,1234,1265,1272 'gesture-composit':64 'gesture-driven':1192 'gesture-overview':39 'gestureangl':450,455,459 'gesturemask':747,1203 'gesturescal':368,374,380 'gesturest':67,68,230,233,336,365,447,587,588,615,657,923,961,968,1009,1129,1259 'give':732 'global':321 'guard':537 'h':831,837,841 'handlechild':709,725,742 'handledoubletap':172,186,581 'handlelongpress':586 'handlepar':715,729,746 'handletripletap':177 'heavi':1062,1161 'height':293,424 'hierarchi':674,1210 'highprioritygestur':685,743,909,1153 'hold':192 'hz':1076 'imag':341,370,498 'includ':755,759,768,775 'init':808 'initi':598 'instead':921 'interact':19,1270 'intermedi':986,1143 'interop':1233 'io':4,11,49,53,56,60,106,111,116,121,126,131,323,397,1034,1041,1052,1055 'ispress':236,240,244,252,1012,1018 'left':797,844 'let':482,490,526,541,559,579,584,801,804,830,1081,1086 'level':1211 'lightweight':1104,1159 'locat':274 'long':203,995 'longpressbeforedrag':527 'longpressgestur':44,45,108,187,208,218,248,528,568,997,1014,1243 'magnif':1126 'magnifi':483 'magnificationgestur':328,1043 'magnify.simultaneously':505 'magnifybi':339,346,350 'magnifygestur':48,52,118,322,348,376,484,1046,1051,1122,1251 'magnifygesture-io':51 'magnifygesture.value':120 'manag':28 'manual':957 'map':564,569 'max':391 'maximumdist':200,221 'min':390 'minimum':311 'minimumangledelta':430 'minimumdist':318,802,809,817,825,826 'minimumdur':194,219,249,264,529,998,1015 'minimumscaledelta':377 'mistak':86,89,883 'mix':1206 'modern':21 'modifi':179,667,675 'move':198 'movement':270 'multi':157 'multi-tap':156 'must':520 'natur':1201 'never':710 'newer':402,1048,1057 'offset':283,295,296,302,308,630,938 'onchang':142,299,433,485,493,948,1065,1078,1099,1156 'one':147,553 'onend':137,143,166,171,176,209,223,258,304,386,465,534,572,646,714,728,741,745,827,903,916,953,1000,1024,1108,1181 'onlongpressgestur':263 'onpressingchang':266 'onswip':805,812,819,840,846,870,880 'ontapgestur':183 'option':516 'overview':38,41,98 'paramet':154 'parent':684,686,694,701,721,733,891 'parent/child':886,1149 'parentact':904,917 'particip':751 'pattern':35 'perform':265,871 'performexpensivehittest':1083,1111 'persist':357,439,613,628,1139,1174 'photo':342,371,499 'pinch':331,1222 'pinch-to-zoom':330,1221 'posit':626 'position.height':635,651 'position.width':632,649 'preced':689 'predictedendtransl':278 'prefer':1047 'press':204,996,1008 'print':855 'prioriti':557,670,679,735 'privat':234,281,337,361,366,414,443,448,616,624,943,969,1010 'problem':700 'properti':591 'protocol':80,84,782,1235 'provid':272,1005,1142 'reason':1178 'recogn':146,476 'rectangl':418,452,852 'red':241 'refer':95,96,1212 'references/gesture-patterns.md':1214,1215 'reorder':1220 'replac':325 'requir':959 'reset':595,620,654,935,958,964,1136 'resettransact':658 'resiz':343 'resolut':31 'resolv':1151 'result':573,576,1082,1089 'return':545 'reusabl':784 'review':13,90,93,1117 'review-checklist':92 'right':798,843 'rotat':410,440,491,507,1127,1226 'rotategestur':55,59,123,396,399,429,457,492,1054,1123,1255 'rotategesture-io':58 'rotategesture.value':125 'rotationeffect':426,453,502 'rotationgestur':405,1060 'roundedrectangl':285 'scale':334,358,488,501,1227 'scale/rotation':1175 'scaledtofit':344 'scaleeffect':243,345,372,500 'second':515,523,539,583 'see':1213 'self.minimumdistance':816 'self.onswipe':818 'sequenc':517,531,1171 'shorthand':178,262 'showmenu':211 'showresult':1002,1026 'similar':1199 'simultan':472,1170 'simultaneousgestur':692,719,726,907,914,1155 'simultaneousgesture.value':511 'sinc':102,1040 'singl':160 'skill':5,6 'sosumi.ai':1237,1241,1245,1249,1253,1257,1261,1267,1274 'sosumi.ai/documentation/swiftui/adding-interactivity-with-gestures)':1273 'sosumi.ai/documentation/swiftui/composing-swiftui-gestures)':1266 'sosumi.ai/documentation/swiftui/draggesture)':1248 'sosumi.ai/documentation/swiftui/gesture)':1236 'sosumi.ai/documentation/swiftui/gesturestate)':1260 'sosumi.ai/documentation/swiftui/longpressgesture)':1244 'sosumi.ai/documentation/swiftui/magnifygesture)':1252 'sosumi.ai/documentation/swiftui/rotategesture)':1256 'sosumi.ai/documentation/swiftui/tapgesture)':1240 'source-dpearson2699' 'space':315 'spatialtapgestur':128 'spatialtapgesture.value':130 'spring':307,661,1197 'startloc':273 'state':27,254,256,280,352,354,360,382,384,413,442,461,463,609,623,642,644,920,926,930,942,977,979,1020,1022,1105,1137 'stay':937 'stream':140 'struct':791 'subview':765,769,771,779 'succeed':188,521,554 'swallow':703 'swift':3,33,159,201,232,279,316,335,359,412,441,481,525,558,614,699,756,790,866,888,927,988,1036,1066 'swiftui':1,9,17,1264 'swiftui/uikit':1231 'swipe':856 'swipegestur':792,854,879 'swipegesture.direction':874 'switch':575 'take':688 'tap':150,158,164,181,702,705,896 'tapgestur':42,43,103,145,165,168,173,561,713,727,740,744,902,915,1239 'tapped.toggle':167 'text':180,737 'three':666 'throttl':1093 'time':480 'toler':217 'track':268,329,406 'transact':659 'transient':606,925,1132 'translat':275 'triggerhapt':225 'tripl':163 'true':212,261,540,1003,1027 'two':408 'two-fing':407 'type':100,1032,1050,1121 'updat':141,144,231,251,349,379,458,639,974,984,1017,1106,1141 'updatemodel':1090 'usag':851 'use':32,151,604,608,718,753,906,919,983,1029,1045,1130,1167,1185,1196 'user':191 'v':833,839,847 'val':580,585 'valu':101,271,300,351,381,387,434,460,466,486,494,509,535,543,599,611,641,647,828,949,976,1079,1100,1109,1133,1140 'value.location':1085,1103,1113 'value.magnification':355,385,393,489 'value.rotation':437,464,469,497 'value.translation':303,645,952,980 'value.translation.height':652,834 'value.translation.width':650,832 'var':235,282,338,362,367,415,444,449,617,625,820,944,970,1011,1186 'variant':1128 'veloc':276,1228 'via':229 'view':72,77,665,673,861,868,877,936,1191,1209 'visual':227,992,1144 'void':105,807,815,875 'vstack':706,722,736,897,910 'width':291,422 'win':682 'withanim':306 'work':1070,1097 'wrap':858 'wrapper':592 'write':14 'x':631 'y':634 'zero':309,622,956 'zoom':333,1224","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-04-22T00:53:45.263Z"},{"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-04-22T05:40:39.293Z"}],"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","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-gestures"},"updatedAt":"2026-04-22T05:40:39.293Z"}}