@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,474 +1,364 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swiftui-gestures
|
|
3
|
-
description: "
|
|
3
|
+
description: "SwiftUI gestures: tap, spatial tap, long press, drag, magnify, rotate, composition (simultaneously, sequenced, exclusively), @GestureState, parent/child conflicts via highPriorityGesture and simultaneousGesture, GestureMask, custom Gesture types. Use when implementing, reviewing or improving gesture handling, resolving conflicts, building pinch, rotate or drag interactions, or migrating MagnificationGesture to MagnifyGesture and RotationGesture to RotateGesture."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# SwiftUI Gestures
|
|
8
|
+
# SwiftUI Gestures
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Recognizing touches in SwiftUI views on iOS 26 with Swift 6.3: which gesture
|
|
11
|
+
type to use, how to keep transient state honest, and how to stop gestures on
|
|
12
|
+
parents and children from fighting. Worked interactions (pinch-to-zoom with
|
|
13
|
+
pan, rotate plus scale, drag-to-reorder, flick-to-dismiss, press-then-drag,
|
|
14
|
+
UIKit interop and accessibility) are in
|
|
15
|
+
[references/gesture-patterns.md](references/gesture-patterns.md).
|
|
13
16
|
|
|
14
|
-
##
|
|
17
|
+
## Gesture overview
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [LongPressGesture](#longpressgesture)
|
|
19
|
-
- [DragGesture](#draggesture)
|
|
20
|
-
- [MagnifyGesture (iOS 17+)](#magnifygesture-ios-17)
|
|
21
|
-
- [RotateGesture (iOS 17+)](#rotategesture-ios-17)
|
|
22
|
-
- [Gesture Composition](#gesture-composition)
|
|
23
|
-
- [`@GestureState`](#gesturestate)
|
|
24
|
-
- [Adding Gestures to Views](#adding-gestures-to-views)
|
|
25
|
-
- [Custom Gesture Protocol](#custom-gesture-protocol)
|
|
26
|
-
- [Common Mistakes](#common-mistakes)
|
|
27
|
-
- [Review Checklist](#review-checklist)
|
|
28
|
-
- [References](#references)
|
|
29
|
-
|
|
30
|
-
## Gesture Overview
|
|
31
|
-
|
|
32
|
-
| Gesture | Type | Value | Since |
|
|
33
|
-
|---|---|---|---|
|
|
19
|
+
| Gesture | Kind | Value | Since |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
34
21
|
| `TapGesture` | Discrete | `Void` | iOS 13 |
|
|
35
22
|
| `LongPressGesture` | Discrete | `Bool` | iOS 13 |
|
|
23
|
+
| `SpatialTapGesture` | Discrete | `SpatialTapGesture.Value` (location) | iOS 16 |
|
|
36
24
|
| `DragGesture` | Continuous | `DragGesture.Value` | iOS 13 |
|
|
37
25
|
| `MagnifyGesture` | Continuous | `MagnifyGesture.Value` | iOS 17 |
|
|
38
26
|
| `RotateGesture` | Continuous | `RotateGesture.Value` | iOS 17 |
|
|
39
|
-
| `SpatialTapGesture` | Discrete | `SpatialTapGesture.Value` | iOS 16 |
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
A discrete gesture reports once, through `.onEnded`. A continuous gesture
|
|
29
|
+
streams values through `.updating` and `.onChanged`, then finishes with
|
|
30
|
+
`.onEnded`.
|
|
43
31
|
|
|
44
32
|
## TapGesture
|
|
45
33
|
|
|
46
|
-
Recognizes one or more taps. Use the `count` parameter for multi-tap.
|
|
47
|
-
|
|
48
34
|
```swift
|
|
49
|
-
|
|
50
|
-
TapGesture()
|
|
51
|
-
TapGesture(count: 2) .onEnded { handleDoubleTap() }
|
|
52
|
-
TapGesture(count: 3) .onEnded { handleTripleTap() }
|
|
35
|
+
Image(systemName: "heart")
|
|
36
|
+
.gesture(TapGesture(count: 2).onEnded { isFavorite.toggle() })
|
|
53
37
|
|
|
54
|
-
// Shorthand
|
|
55
|
-
|
|
38
|
+
// Shorthand
|
|
39
|
+
Image(systemName: "heart")
|
|
40
|
+
.onTapGesture(count: 2) { isFavorite.toggle() }
|
|
56
41
|
```
|
|
57
42
|
|
|
43
|
+
For a plain single tap that performs an action, use a `Button` instead (see
|
|
44
|
+
Common mistakes).
|
|
45
|
+
|
|
58
46
|
## LongPressGesture
|
|
59
47
|
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
The press succeeds once the finger has stayed down for `minimumDuration`
|
|
49
|
+
(0.5 seconds unless you say otherwise). It fails if the finger travels further
|
|
50
|
+
than `maximumDistance` first.
|
|
62
51
|
|
|
63
52
|
```swift
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
struct HoldToConfirm: View {
|
|
54
|
+
@GestureState private var holding = false
|
|
55
|
+
@State private var confirmed = false
|
|
56
|
+
|
|
57
|
+
var body: some View {
|
|
58
|
+
Circle()
|
|
59
|
+
.fill(confirmed ? .green : (holding ? .orange : .gray))
|
|
60
|
+
.frame(width: 88, height: 88)
|
|
61
|
+
.scaleEffect(holding ? 1.15 : 1)
|
|
62
|
+
.animation(.easeOut(duration: 0.2), value: holding)
|
|
63
|
+
.gesture(
|
|
64
|
+
LongPressGesture(minimumDuration: 1.2, maximumDistance: 12)
|
|
65
|
+
.updating($holding) { pressed, state, _ in state = pressed }
|
|
66
|
+
.onEnded { _ in confirmed = true }
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
71
70
|
```
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
The shorthand takes a pressing callback for the same feedback:
|
|
74
73
|
|
|
75
74
|
```swift
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.gesture(
|
|
82
|
-
LongPressGesture(minimumDuration: 0.8)
|
|
83
|
-
.updating($isPressing) { current, state, _ in state = current }
|
|
84
|
-
.onEnded { _ in completedLongPress = true }
|
|
85
|
-
)
|
|
75
|
+
.onLongPressGesture(minimumDuration: 1.2) {
|
|
76
|
+
confirmed = true
|
|
77
|
+
} onPressingChanged: { pressed in
|
|
78
|
+
highlight = pressed
|
|
79
|
+
}
|
|
86
80
|
```
|
|
87
81
|
|
|
88
|
-
Shorthand: `.onLongPressGesture(minimumDuration:perform:onPressingChanged:)`.
|
|
89
|
-
|
|
90
82
|
## DragGesture
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
`
|
|
84
|
+
`DragGesture.Value` carries `startLocation`, `location`, `translation`,
|
|
85
|
+
`velocity` and `predictedEndTranslation`.
|
|
94
86
|
|
|
95
87
|
```swift
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
struct SnapBackTile: View {
|
|
89
|
+
@State private var shift = CGSize.zero
|
|
90
|
+
|
|
91
|
+
var body: some View {
|
|
92
|
+
RoundedRectangle(cornerRadius: 20)
|
|
93
|
+
.fill(.indigo)
|
|
94
|
+
.frame(width: 140, height: 140)
|
|
95
|
+
.offset(shift)
|
|
96
|
+
.gesture(
|
|
97
|
+
DragGesture()
|
|
98
|
+
.onChanged { drag in shift = drag.translation }
|
|
99
|
+
.onEnded { _ in withAnimation(.spring) { shift = .zero } }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
107
103
|
```
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```swift
|
|
112
|
-
DragGesture(minimumDistance: 20, coordinateSpace: .global)
|
|
113
|
-
```
|
|
105
|
+
`DragGesture(minimumDistance: 20, coordinateSpace: .global)` waits for 20
|
|
106
|
+
points of travel and reports locations in window coordinates.
|
|
114
107
|
|
|
115
108
|
## MagnifyGesture (iOS 17+)
|
|
116
109
|
|
|
117
|
-
|
|
110
|
+
`MagnifyGesture` supersedes `MagnificationGesture`. Its value exposes
|
|
111
|
+
`magnification`.
|
|
112
|
+
|
|
113
|
+
Transient zoom that springs back when the fingers lift:
|
|
118
114
|
|
|
119
115
|
```swift
|
|
120
|
-
@GestureState private var
|
|
116
|
+
@GestureState private var pinch: CGFloat = 1
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
.scaleEffect(magnifyBy)
|
|
118
|
+
photo
|
|
119
|
+
.scaleEffect(pinch)
|
|
125
120
|
.gesture(
|
|
126
|
-
MagnifyGesture()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
121
|
+
MagnifyGesture().updating($pinch) { current, live, _ in
|
|
122
|
+
live = current.magnification
|
|
123
|
+
}
|
|
130
124
|
)
|
|
131
125
|
```
|
|
132
126
|
|
|
133
|
-
|
|
127
|
+
Zoom that sticks, clamped when the gesture ends:
|
|
134
128
|
|
|
135
129
|
```swift
|
|
136
|
-
@State private var
|
|
137
|
-
@GestureState private var
|
|
130
|
+
@State private var zoom: CGFloat = 1
|
|
131
|
+
@GestureState private var livePinch: CGFloat = 1
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
.scaleEffect(
|
|
133
|
+
photo
|
|
134
|
+
.scaleEffect(zoom * livePinch)
|
|
141
135
|
.gesture(
|
|
142
136
|
MagnifyGesture(minimumScaleDelta: 0.01)
|
|
143
|
-
.updating($
|
|
144
|
-
.onEnded {
|
|
145
|
-
|
|
137
|
+
.updating($livePinch) { current, live, _ in live = current.magnification }
|
|
138
|
+
.onEnded { final in
|
|
139
|
+
zoom = min(max(zoom * final.magnification, 0.5), 5.0)
|
|
146
140
|
}
|
|
147
141
|
)
|
|
148
142
|
```
|
|
149
143
|
|
|
150
144
|
## RotateGesture (iOS 17+)
|
|
151
145
|
|
|
152
|
-
`RotateGesture`
|
|
146
|
+
`RotateGesture` supersedes `RotationGesture`. Its `rotation` is an `Angle`.
|
|
153
147
|
|
|
154
148
|
```swift
|
|
155
|
-
@State private var
|
|
149
|
+
@State private var turned = Angle.zero
|
|
150
|
+
@GestureState private var liveTurn = Angle.zero
|
|
156
151
|
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
.rotationEffect(angle)
|
|
152
|
+
dial
|
|
153
|
+
.rotationEffect(turned + liveTurn)
|
|
160
154
|
.gesture(
|
|
161
155
|
RotateGesture(minimumAngleDelta: .degrees(1))
|
|
162
|
-
.
|
|
156
|
+
.updating($liveTurn) { current, live, _ in live = current.rotation }
|
|
157
|
+
.onEnded { final in turned += final.rotation }
|
|
163
158
|
)
|
|
164
159
|
```
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```swift
|
|
169
|
-
@State private var currentAngle = Angle.zero
|
|
170
|
-
@GestureState private var gestureAngle = Angle.zero
|
|
171
|
-
|
|
172
|
-
Rectangle()
|
|
173
|
-
.rotationEffect(currentAngle + gestureAngle)
|
|
174
|
-
.gesture(
|
|
175
|
-
RotateGesture()
|
|
176
|
-
.updating($gestureAngle) { value, state, _ in state = value.rotation }
|
|
177
|
-
.onEnded { value in currentAngle += value.rotation }
|
|
178
|
-
)
|
|
179
|
-
```
|
|
161
|
+
## Gesture composition
|
|
180
162
|
|
|
181
|
-
|
|
163
|
+
### simultaneously(with:)
|
|
182
164
|
|
|
183
|
-
|
|
165
|
+
Both gestures recognize together. The value is `SimultaneousGesture.Value`,
|
|
166
|
+
with optional `first` and `second`.
|
|
184
167
|
|
|
185
168
|
```swift
|
|
186
|
-
let
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
Image("photo")
|
|
193
|
-
.scaleEffect(scale)
|
|
194
|
-
.rotationEffect(angle)
|
|
195
|
-
.gesture(magnify.simultaneously(with: rotate))
|
|
169
|
+
let pinchAndTwist = MagnifyGesture()
|
|
170
|
+
.simultaneously(with: RotateGesture())
|
|
171
|
+
.onChanged { both in
|
|
172
|
+
liveScale = both.first?.magnification ?? 1
|
|
173
|
+
liveAngle = both.second?.rotation ?? .zero
|
|
174
|
+
}
|
|
196
175
|
```
|
|
197
176
|
|
|
198
|
-
|
|
177
|
+
### sequenced(before:)
|
|
199
178
|
|
|
200
|
-
|
|
179
|
+
The second gesture can start only after the first one succeeds.
|
|
201
180
|
|
|
202
181
|
```swift
|
|
203
|
-
let
|
|
182
|
+
let holdThenMove = LongPressGesture(minimumDuration: 0.4)
|
|
204
183
|
.sequenced(before: DragGesture())
|
|
205
|
-
.onEnded {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
184
|
+
.onEnded { outcome in
|
|
185
|
+
if case .second(true, let drag?) = outcome {
|
|
186
|
+
anchor.x += drag.translation.width
|
|
187
|
+
anchor.y += drag.translation.height
|
|
188
|
+
}
|
|
209
189
|
}
|
|
210
190
|
```
|
|
211
191
|
|
|
212
|
-
###
|
|
192
|
+
### exclusively(before:)
|
|
193
|
+
|
|
194
|
+
Only one gesture wins, and the first one listed gets priority. Map both to a
|
|
195
|
+
shared type to keep the handler simple:
|
|
213
196
|
|
|
214
197
|
```swift
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.map { _ in ExclusiveResult.longPress }
|
|
220
|
-
)
|
|
198
|
+
enum CardAction { case open, archive }
|
|
199
|
+
|
|
200
|
+
let openOrArchive = TapGesture(count: 2).map { CardAction.archive }
|
|
201
|
+
.exclusively(before: TapGesture().map { CardAction.open })
|
|
221
202
|
.onEnded { result in
|
|
222
203
|
switch result {
|
|
223
|
-
case .first(let
|
|
224
|
-
|
|
204
|
+
case .first(let action), .second(let action):
|
|
205
|
+
handle(action)
|
|
225
206
|
}
|
|
226
207
|
}
|
|
227
208
|
```
|
|
228
209
|
|
|
229
|
-
##
|
|
210
|
+
## @GestureState
|
|
230
211
|
|
|
231
|
-
`@GestureState`
|
|
232
|
-
|
|
233
|
-
|
|
212
|
+
`@GestureState` returns to its initial value on its own when the gesture ends
|
|
213
|
+
or is cancelled. Use it for feedback that only exists while a finger is down.
|
|
214
|
+
Keep anything that must survive the gesture in `@State`.
|
|
234
215
|
|
|
235
216
|
```swift
|
|
236
|
-
|
|
237
|
-
@State private var
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
217
|
+
struct DraggableSticker: View {
|
|
218
|
+
@State private var resting = CGPoint(x: 160, y: 300)
|
|
219
|
+
@GestureState private var inFlight = CGSize.zero
|
|
220
|
+
|
|
221
|
+
var body: some View {
|
|
222
|
+
Text("Sticker")
|
|
223
|
+
.padding()
|
|
224
|
+
.background(.yellow, in: .capsule)
|
|
225
|
+
.position(x: resting.x + inFlight.width, y: resting.y + inFlight.height)
|
|
226
|
+
.gesture(
|
|
227
|
+
DragGesture()
|
|
228
|
+
.updating($inFlight) { drag, state, _ in state = drag.translation }
|
|
229
|
+
.onEnded { drag in
|
|
230
|
+
resting.x += drag.translation.width
|
|
231
|
+
resting.y += drag.translation.height
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
254
236
|
```
|
|
255
237
|
|
|
256
|
-
|
|
238
|
+
To animate the reset, give the state a reset transaction:
|
|
257
239
|
|
|
258
|
-
|
|
240
|
+
```swift
|
|
241
|
+
@GestureState(resetTransaction: Transaction(animation: .spring)) private var inFlight = CGSize.zero
|
|
242
|
+
```
|
|
259
243
|
|
|
260
|
-
|
|
244
|
+
## Adding gestures to views
|
|
261
245
|
|
|
262
|
-
| Modifier |
|
|
263
|
-
|
|
264
|
-
| `.gesture()` |
|
|
265
|
-
| `.highPriorityGesture()` |
|
|
266
|
-
| `.simultaneousGesture()` | Both
|
|
246
|
+
| Modifier | Who wins when parent and child both recognize |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `.gesture(_:)` | The child. Gestures on subviews take precedence. |
|
|
249
|
+
| `.highPriorityGesture(_:)` | The parent's gesture, ahead of the child's. |
|
|
250
|
+
| `.simultaneousGesture(_:)` | Both fire. |
|
|
267
251
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
.gesture(TapGesture().onEnded { handleParent() })
|
|
252
|
+
A typical conflict: a card attaches `.gesture(TapGesture()...)` to open a
|
|
253
|
+
detail screen, and a `Button` inside the card toggles a bookmark. With plain
|
|
254
|
+
`.gesture`, the button keeps priority, so a tap on it never reaches the card's
|
|
255
|
+
handler. Choose deliberately:
|
|
274
256
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
.simultaneousGesture(TapGesture().onEnded { handleParent() })
|
|
257
|
+
```swift
|
|
258
|
+
// The card should also react when the button is tapped (for example, to log the interaction):
|
|
259
|
+
card.simultaneousGesture(TapGesture().onEnded { logCardTap() })
|
|
280
260
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
Text("Child")
|
|
284
|
-
.gesture(TapGesture().onEnded { handleChild() })
|
|
285
|
-
}
|
|
286
|
-
.highPriorityGesture(TapGesture().onEnded { handleParent() })
|
|
261
|
+
// The card must take the tap even over its children (the button stops responding):
|
|
262
|
+
card.highPriorityGesture(TapGesture().onEnded { openDetail() })
|
|
287
263
|
```
|
|
288
264
|
|
|
289
265
|
### GestureMask
|
|
290
266
|
|
|
291
|
-
|
|
267
|
+
`.gesture(_:including:)` narrows which gestures stay active at this level of
|
|
268
|
+
the hierarchy:
|
|
269
|
+
|
|
270
|
+
| Mask | Effect |
|
|
271
|
+
| --- | --- |
|
|
272
|
+
| `.all` | This gesture and subview gestures (the default) |
|
|
273
|
+
| `.gesture` | Only this gesture; subview gestures are disabled |
|
|
274
|
+
| `.subviews` | Only subview gestures; this gesture is disabled |
|
|
275
|
+
| `.none` | Neither |
|
|
292
276
|
|
|
293
277
|
```swift
|
|
294
|
-
.gesture(
|
|
295
|
-
.gesture(drag, including: .subviews) // only subview gestures
|
|
296
|
-
.gesture(drag, including: .all) // default: this + subviews
|
|
278
|
+
canvas.gesture(panGesture, including: isEditing ? .subviews : .all)
|
|
297
279
|
```
|
|
298
280
|
|
|
299
|
-
## Custom Gesture
|
|
281
|
+
## Custom Gesture protocol
|
|
300
282
|
|
|
301
|
-
|
|
283
|
+
Wrap a configured gesture in its own type by conforming to `Gesture` and
|
|
284
|
+
returning the pieces from `var body: some Gesture`.
|
|
302
285
|
|
|
303
286
|
```swift
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
init(minimumDistance: CGFloat = 50, onSwipe: @escaping (Direction) -> Void) {
|
|
310
|
-
self.minimumDistance = minimumDistance
|
|
311
|
-
self.onSwipe = onSwipe
|
|
312
|
-
}
|
|
287
|
+
enum FlingDirection { case left, right, up, down }
|
|
288
|
+
|
|
289
|
+
struct FlingGesture: Gesture {
|
|
290
|
+
var threshold: CGFloat = 50
|
|
291
|
+
var onFling: (FlingDirection) -> Void
|
|
313
292
|
|
|
314
293
|
var body: some Gesture {
|
|
315
|
-
DragGesture(minimumDistance:
|
|
316
|
-
.onEnded {
|
|
317
|
-
let
|
|
318
|
-
|
|
319
|
-
|
|
294
|
+
DragGesture(minimumDistance: threshold)
|
|
295
|
+
.onEnded { drag in
|
|
296
|
+
let dx = drag.translation.width
|
|
297
|
+
let dy = drag.translation.height
|
|
298
|
+
if abs(dx) >= abs(dy) {
|
|
299
|
+
onFling(dx > 0 ? .right : .left)
|
|
320
300
|
} else {
|
|
321
|
-
|
|
301
|
+
onFling(dy > 0 ? .down : .up)
|
|
322
302
|
}
|
|
323
303
|
}
|
|
324
304
|
}
|
|
325
305
|
}
|
|
326
306
|
|
|
327
|
-
// Usage
|
|
328
|
-
Rectangle().gesture(SwipeGesture { print("Swiped \($0)") })
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
Wrap in a `View` extension for ergonomic API:
|
|
332
|
-
|
|
333
|
-
```swift
|
|
334
307
|
extension View {
|
|
335
|
-
func
|
|
336
|
-
gesture(
|
|
308
|
+
func onFling(threshold: CGFloat = 50, perform action: @escaping (FlingDirection) -> Void) -> some View {
|
|
309
|
+
gesture(FlingGesture(threshold: threshold, onFling: action))
|
|
337
310
|
}
|
|
338
311
|
}
|
|
339
312
|
```
|
|
340
313
|
|
|
341
|
-
## Common
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
```swift
|
|
381
|
-
// DON'T: No visual feedback during long press
|
|
382
|
-
LongPressGesture(minimumDuration: 2.0)
|
|
383
|
-
.onEnded { _ in showResult = true }
|
|
384
|
-
|
|
385
|
-
// DO: Provide feedback while pressing
|
|
386
|
-
@GestureState private var isPressing = false
|
|
387
|
-
|
|
388
|
-
LongPressGesture(minimumDuration: 2.0)
|
|
389
|
-
.updating($isPressing) { current, state, _ in
|
|
390
|
-
state = current
|
|
391
|
-
}
|
|
392
|
-
.onEnded { _ in showResult = true }
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### 4. Using deprecated gesture types on iOS 17+
|
|
396
|
-
|
|
397
|
-
```swift
|
|
398
|
-
// DON'T: Deprecated since iOS 17
|
|
399
|
-
MagnificationGesture() // deprecated - use MagnifyGesture()
|
|
400
|
-
|
|
401
|
-
// DO: Use newer gesture types
|
|
402
|
-
MagnifyGesture() // iOS 17+
|
|
403
|
-
RotateGesture() // iOS 17+ (newer alternative to RotationGesture)
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### 5. Heavy computation in onChanged
|
|
407
|
-
|
|
408
|
-
```swift
|
|
409
|
-
// DON'T: Expensive work called every frame (~60-120 Hz)
|
|
410
|
-
DragGesture()
|
|
411
|
-
.onChanged { value in
|
|
412
|
-
let result = performExpensiveHitTest(at: value.location)
|
|
413
|
-
let filtered = applyComplexFilter(result)
|
|
414
|
-
updateModel(filtered)
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// DO: Throttle or defer expensive work
|
|
418
|
-
DragGesture()
|
|
419
|
-
.onChanged { value in
|
|
420
|
-
dragPosition = value.location // lightweight state update only
|
|
421
|
-
}
|
|
422
|
-
.onEnded { value in
|
|
423
|
-
performExpensiveHitTest(at: value.location) // once at end
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### 6. Using onTapGesture for actions that should be a Button
|
|
428
|
-
|
|
429
|
-
```swift
|
|
430
|
-
// DON'T: onTapGesture has no accessibility traits, VoiceOver role,
|
|
431
|
-
// Voice Control targeting, Switch Control scanning, or keyboard activation
|
|
432
|
-
Text("Delete")
|
|
433
|
-
.onTapGesture { deleteItem() }
|
|
434
|
-
|
|
435
|
-
// DO: Button provides all of these automatically
|
|
436
|
-
Button("Delete", role: .destructive) { deleteItem() }
|
|
437
|
-
|
|
438
|
-
// DO: For custom visuals, use ButtonStyle instead of onTapGesture
|
|
439
|
-
Button { toggleExpanded() } label: {
|
|
440
|
-
CardView()
|
|
441
|
-
}
|
|
442
|
-
.buttonStyle(.plain)
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Reserve `onTapGesture` for multi-tap (`count: 2+`), tap-location-dependent
|
|
446
|
-
behavior, or adding tap recognition to non-interactive content that already
|
|
447
|
-
has appropriate accessibility traits.
|
|
448
|
-
|
|
449
|
-
## Review Checklist
|
|
450
|
-
|
|
451
|
-
- [ ] Correct gesture type: `MagnifyGesture`/`RotateGesture` (not deprecated `Magnification`/`Rotation` variants)
|
|
452
|
-
- [ ] `@GestureState` used for transient values that should reset; `@State` for persisted values
|
|
453
|
-
- [ ] `.updating()` provides intermediate visual feedback during continuous gestures
|
|
454
|
-
- [ ] Parent/child conflicts resolved with `.highPriorityGesture()` or `.simultaneousGesture()`
|
|
455
|
-
- [ ] `onChanged` closures are lightweight - no heavy computation every frame
|
|
456
|
-
- [ ] Composed gestures use correct combinator: `simultaneously`, `sequenced`, or `exclusively`
|
|
457
|
-
- [ ] Persisted scale/rotation clamped to reasonable bounds in `onEnded`
|
|
458
|
-
- [ ] Custom `Gesture` conformances use `var body: some Gesture` (not `View`)
|
|
459
|
-
- [ ] Gesture-driven animations use `.spring` or similar for natural deceleration
|
|
460
|
-
- [ ] `GestureMask` considered when mixing gestures across view hierarchy levels
|
|
461
|
-
- [ ] `onTapGesture` only used where `count > 1`, tap location, or coordinate space matters - plain single-tap actions use `Button` instead
|
|
314
|
+
## Common mistakes
|
|
315
|
+
|
|
316
|
+
1. **Parent and child gestures fighting.** A parent's `.gesture` loses to its
|
|
317
|
+
children. Decide which should win and use `.simultaneousGesture` or
|
|
318
|
+
`.highPriorityGesture` accordingly.
|
|
319
|
+
2. **`@State` for a drag offset that should not persist.** It needs a manual
|
|
320
|
+
reset, and a cancelled gesture can leave the view displaced. Use
|
|
321
|
+
`@GestureState` with `.updating`.
|
|
322
|
+
3. **Long presses with no feedback while held.** Add `.updating` (or
|
|
323
|
+
`onPressingChanged`) so the user sees the press registering.
|
|
324
|
+
4. **New code on `MagnificationGesture` or `RotationGesture`.** Both are renamed
|
|
325
|
+
to `MagnifyGesture` and `RotateGesture` and marked for deprecation. The
|
|
326
|
+
iOS 26 SDK does not warn yet, but new code on iOS 17 or later should use the
|
|
327
|
+
replacements.
|
|
328
|
+
5. **Heavy work in `onChanged`.** It runs on every touch update, often 60 to
|
|
329
|
+
120 times a second. Keep it to assignments; move expensive work to
|
|
330
|
+
`onEnded` or throttle it.
|
|
331
|
+
6. **`onTapGesture` as a button.** It adds no button trait, no VoiceOver
|
|
332
|
+
action, no Voice Control or Switch Control target and no keyboard
|
|
333
|
+
activation. Use `Button` (with a role such as `.destructive` where it
|
|
334
|
+
applies), and `.buttonStyle(.plain)` when the visuals are custom.
|
|
335
|
+
7. **When `onTapGesture` is right.** Multi-tap counts, handlers that need the
|
|
336
|
+
tap location, or non-interactive content that you have given proper
|
|
337
|
+
accessibility traits.
|
|
338
|
+
|
|
339
|
+
## Review checklist
|
|
340
|
+
|
|
341
|
+
- [ ] `MagnifyGesture` and `RotateGesture`, not the older names
|
|
342
|
+
- [ ] `@GestureState` for transient values, `@State` for persisted ones
|
|
343
|
+
- [ ] Continuous gestures give live feedback through `.updating`
|
|
344
|
+
- [ ] Parent and child conflicts resolved with the right priority modifier
|
|
345
|
+
- [ ] `onChanged` handlers stay light
|
|
346
|
+
- [ ] Combinator matches intent: simultaneous, sequenced or exclusive
|
|
347
|
+
- [ ] Persisted scale and rotation clamped in `onEnded`
|
|
348
|
+
- [ ] Custom gestures expose `var body: some Gesture`
|
|
349
|
+
- [ ] Gesture-driven motion settles with `.spring` or a similar animation
|
|
350
|
+
- [ ] `GestureMask` considered where several hierarchy levels attach gestures
|
|
351
|
+
- [ ] `onTapGesture` used only for counts above one, tap location or coordinate space needs
|
|
462
352
|
|
|
463
353
|
## References
|
|
464
354
|
|
|
465
|
-
-
|
|
466
|
-
- [Gesture
|
|
467
|
-
- [TapGesture](https://
|
|
468
|
-
- [LongPressGesture](https://
|
|
469
|
-
- [DragGesture](https://
|
|
470
|
-
- [MagnifyGesture](https://
|
|
471
|
-
- [RotateGesture](https://
|
|
472
|
-
- [GestureState](https://
|
|
473
|
-
- [Composing SwiftUI gestures](https://
|
|
474
|
-
- [Adding interactivity with gestures](https://
|
|
355
|
+
- [references/gesture-patterns.md](references/gesture-patterns.md)
|
|
356
|
+
- [Gesture](https://developer.apple.com/documentation/swiftui/gesture)
|
|
357
|
+
- [TapGesture](https://developer.apple.com/documentation/swiftui/tapgesture)
|
|
358
|
+
- [LongPressGesture](https://developer.apple.com/documentation/swiftui/longpressgesture)
|
|
359
|
+
- [DragGesture](https://developer.apple.com/documentation/swiftui/draggesture)
|
|
360
|
+
- [MagnifyGesture](https://developer.apple.com/documentation/swiftui/magnifygesture)
|
|
361
|
+
- [RotateGesture](https://developer.apple.com/documentation/swiftui/rotategesture)
|
|
362
|
+
- [GestureState](https://developer.apple.com/documentation/swiftui/gesturestate)
|
|
363
|
+
- [Composing SwiftUI gestures](https://developer.apple.com/documentation/swiftui/composing-swiftui-gestures)
|
|
364
|
+
- [Adding interactivity with gestures](https://developer.apple.com/documentation/swiftui/adding-interactivity-with-gestures)
|