@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,435 +1,353 @@
|
|
|
1
|
-
# Gesture Patterns
|
|
1
|
+
# Gesture Patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Worked interactions for the `swiftui-gestures` skill. The SKILL.md explains the
|
|
4
|
+
gesture types, composition and `@GestureState`; this file assumes that
|
|
5
|
+
material.
|
|
5
6
|
|
|
6
7
|
## Contents
|
|
7
8
|
|
|
8
|
-
- [Pinch-to-
|
|
9
|
-
- [Combined
|
|
10
|
-
- [Drag-to-
|
|
11
|
-
- [Gesture
|
|
12
|
-
- [Long-
|
|
13
|
-
- [SwiftUI
|
|
14
|
-
- [Accessibility
|
|
9
|
+
- [Pinch-to-zoom with MagnifyGesture](#pinch-to-zoom-with-magnifygesture)
|
|
10
|
+
- [Combined rotate and scale](#combined-rotate-and-scale)
|
|
11
|
+
- [Drag-to-reorder](#drag-to-reorder)
|
|
12
|
+
- [Gesture velocity calculations](#gesture-velocity-calculations)
|
|
13
|
+
- [Long-press then drag](#long-press-then-drag)
|
|
14
|
+
- [SwiftUI and UIKit gesture interop](#swiftui-and-uikit-gesture-interop)
|
|
15
|
+
- [Accessibility considerations](#accessibility-considerations)
|
|
15
16
|
|
|
16
|
-
## Pinch-to-
|
|
17
|
+
## Pinch-to-zoom with MagnifyGesture
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
A zoomable image that can also be panned once it is enlarged, and double-tapped
|
|
20
|
+
back to its original size.
|
|
19
21
|
|
|
20
22
|
```swift
|
|
21
|
-
struct
|
|
22
|
-
|
|
23
|
-
@State private var currentOffset = CGSize.zero
|
|
24
|
-
@GestureState private var gestureScale = 1.0
|
|
25
|
-
@GestureState private var dragOffset = CGSize.zero
|
|
23
|
+
struct ZoomableImage: View {
|
|
24
|
+
let image: Image
|
|
26
25
|
|
|
27
|
-
private let
|
|
28
|
-
private let
|
|
26
|
+
private let smallestZoom: CGFloat = 0.5
|
|
27
|
+
private let largestZoom: CGFloat = 5.0
|
|
28
|
+
|
|
29
|
+
@State private var zoom: CGFloat = 1
|
|
30
|
+
@State private var pan = CGSize.zero
|
|
31
|
+
@GestureState private var livePinch: CGFloat = 1
|
|
32
|
+
@GestureState private var livePan = CGSize.zero
|
|
29
33
|
|
|
30
34
|
var body: some View {
|
|
31
|
-
|
|
35
|
+
image
|
|
32
36
|
.resizable()
|
|
33
37
|
.scaledToFit()
|
|
34
|
-
.scaleEffect(
|
|
35
|
-
.offset(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
.gesture(magnifyGesture)
|
|
40
|
-
.simultaneousGesture(panGesture)
|
|
38
|
+
.scaleEffect(zoom * livePinch)
|
|
39
|
+
.offset(x: pan.width + livePan.width, y: pan.height + livePan.height)
|
|
40
|
+
.gesture(pinch)
|
|
41
|
+
.simultaneousGesture(drag)
|
|
41
42
|
.onTapGesture(count: 2) {
|
|
42
43
|
withAnimation(.spring) {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
zoom = 1
|
|
45
|
+
pan = .zero
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
private var
|
|
50
|
+
private var pinch: some Gesture {
|
|
50
51
|
MagnifyGesture()
|
|
51
|
-
.updating($
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.onEnded { value in
|
|
55
|
-
let newScale = currentScale * value.magnification
|
|
52
|
+
.updating($livePinch) { current, live, _ in live = current.magnification }
|
|
53
|
+
.onEnded { final in
|
|
54
|
+
let proposed = zoom * final.magnification
|
|
56
55
|
withAnimation(.spring) {
|
|
57
|
-
|
|
56
|
+
zoom = min(max(proposed, smallestZoom), largestZoom)
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
private var
|
|
61
|
+
private var drag: some Gesture {
|
|
63
62
|
DragGesture()
|
|
64
|
-
.updating($
|
|
65
|
-
|
|
66
|
-
state = value.translation
|
|
63
|
+
.updating($livePan) { current, live, _ in
|
|
64
|
+
if zoom > 1 { live = current.translation }
|
|
67
65
|
}
|
|
68
|
-
.onEnded {
|
|
69
|
-
guard
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
.onEnded { final in
|
|
67
|
+
guard zoom > 1 else { return }
|
|
68
|
+
pan.width += final.translation.width
|
|
69
|
+
pan.height += final.translation.height
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
```
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
Panning only applies while `zoom` is above 1, so at normal size a drag does
|
|
76
|
+
nothing and the image cannot wander off screen. The double tap uses
|
|
77
|
+
`onTapGesture(count:)`, which is the legitimate use of that modifier.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
## Combined rotate and scale
|
|
80
80
|
|
|
81
81
|
```swift
|
|
82
|
-
struct
|
|
83
|
-
@State private var
|
|
84
|
-
@State private var
|
|
85
|
-
@GestureState private var
|
|
86
|
-
@GestureState private var
|
|
82
|
+
struct TurnAndScaleSticker: View {
|
|
83
|
+
@State private var angle: Angle = .zero
|
|
84
|
+
@State private var size: CGFloat = 1
|
|
85
|
+
@GestureState private var liveAngle = Angle.zero
|
|
86
|
+
@GestureState private var liveSize: CGFloat = 1
|
|
87
87
|
|
|
88
88
|
var body: some View {
|
|
89
|
-
Image("
|
|
90
|
-
|
|
91
|
-
.
|
|
92
|
-
.
|
|
93
|
-
.
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
.onEnded { value in
|
|
109
|
-
currentScale *= value.magnification
|
|
110
|
-
currentScale = min(max(currentScale, 0.3), 5.0)
|
|
111
|
-
}
|
|
112
|
-
)
|
|
113
|
-
)
|
|
89
|
+
let sticker = Image(systemName: "star.fill")
|
|
90
|
+
return sticker
|
|
91
|
+
.font(.system(size: 80))
|
|
92
|
+
.rotationEffect(angle + liveAngle)
|
|
93
|
+
.scaleEffect(size * liveSize)
|
|
94
|
+
.gesture(twist.simultaneously(with: stretch))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private var twist: some Gesture {
|
|
98
|
+
RotateGesture()
|
|
99
|
+
.updating($liveAngle) { current, live, _ in live = current.rotation }
|
|
100
|
+
.onEnded { final in angle += final.rotation }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private var stretch: some Gesture {
|
|
104
|
+
MagnifyGesture()
|
|
105
|
+
.updating($liveSize) { current, live, _ in live = current.magnification }
|
|
106
|
+
.onEnded { final in size = min(max(size * final.magnification, 0.3), 5.0) }
|
|
114
107
|
}
|
|
115
108
|
}
|
|
116
109
|
```
|
|
117
110
|
|
|
118
|
-
## Drag-to-
|
|
111
|
+
## Drag-to-reorder
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
A short press lifts a row; dragging then moves it. The press and the drag are
|
|
114
|
+
sequenced so a normal scroll does not start a reorder.
|
|
121
115
|
|
|
122
116
|
```swift
|
|
123
|
-
struct
|
|
124
|
-
@State private var
|
|
125
|
-
@State private var
|
|
126
|
-
@State private var
|
|
117
|
+
struct ReorderableStack: View {
|
|
118
|
+
@State private var stops = ["Depot", "Market", "School", "Clinic", "Harbour"]
|
|
119
|
+
@State private var lifted: String?
|
|
120
|
+
@State private var lift = CGFloat.zero
|
|
121
|
+
|
|
122
|
+
private let rowPitch: CGFloat = 50
|
|
127
123
|
|
|
128
124
|
var body: some View {
|
|
129
|
-
VStack {
|
|
130
|
-
ForEach(
|
|
131
|
-
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
case .first(true):
|
|
140
|
-
withAnimation(.spring) {
|
|
141
|
-
draggingItem = item
|
|
142
|
-
}
|
|
143
|
-
case .second(true, let drag):
|
|
144
|
-
dragOffset = drag?.translation ?? .zero
|
|
145
|
-
updateOrder(for: item, translation: dragOffset.height)
|
|
146
|
-
default:
|
|
147
|
-
break
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
.onEnded { _ in
|
|
151
|
-
withAnimation(.spring) {
|
|
152
|
-
draggingItem = nil
|
|
153
|
-
dragOffset = .zero
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
)
|
|
125
|
+
VStack(alignment: .center, spacing: 0) {
|
|
126
|
+
ForEach(stops, id: \.self) { stop in
|
|
127
|
+
Text(stop)
|
|
128
|
+
.frame(maxWidth: .infinity, minHeight: rowPitch)
|
|
129
|
+
.background(lifted == stop ? Color.accentColor.opacity(0.2) : Color.clear)
|
|
130
|
+
.shadow(radius: lifted == stop ? 6 : 0)
|
|
131
|
+
.scaleEffect(lifted == stop ? 1.05 : 1)
|
|
132
|
+
.offset(y: lifted == stop ? lift : 0)
|
|
133
|
+
.zIndex(lifted == stop ? 1 : 0)
|
|
134
|
+
.gesture(reorder(stop))
|
|
157
135
|
}
|
|
158
136
|
}
|
|
159
137
|
}
|
|
160
138
|
|
|
161
|
-
private func
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
139
|
+
private func reorder(_ stop: String) -> some Gesture {
|
|
140
|
+
let liftThenSlide = LongPressGesture(minimumDuration: 0.35).sequenced(before: DragGesture())
|
|
141
|
+
return liftThenSlide
|
|
142
|
+
.onChanged { phase in
|
|
143
|
+
switch phase {
|
|
144
|
+
case .first(true):
|
|
145
|
+
withAnimation(.spring) { lifted = stop }
|
|
146
|
+
case .second(true, let drag?):
|
|
147
|
+
lift = drag.translation.height
|
|
148
|
+
default:
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
.onEnded { _ in
|
|
153
|
+
commitMove(of: stop)
|
|
154
|
+
withAnimation(.spring) {
|
|
155
|
+
lifted = nil
|
|
156
|
+
lift = 0
|
|
157
|
+
}
|
|
173
158
|
}
|
|
174
|
-
}
|
|
175
159
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
struct ItemRow: View {
|
|
179
|
-
let title: String
|
|
180
|
-
let isDragging: Bool
|
|
181
160
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
.scaleEffect(isDragging ? 1.05 : 1.0)
|
|
161
|
+
private func commitMove(of stop: String) {
|
|
162
|
+
guard let from = stops.firstIndex(of: stop) else { return }
|
|
163
|
+
let steps = Int((lift / rowPitch).rounded())
|
|
164
|
+
let to = min(max(from + steps, 0), stops.count - 1)
|
|
165
|
+
guard to != from else { return }
|
|
166
|
+
stops.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
|
|
189
167
|
}
|
|
190
168
|
}
|
|
191
169
|
```
|
|
192
170
|
|
|
193
|
-
|
|
171
|
+
`move(fromOffsets:toOffset:)` inserts before the destination offset, which is
|
|
172
|
+
why a downward move adds one.
|
|
173
|
+
|
|
174
|
+
## Gesture velocity calculations
|
|
194
175
|
|
|
195
|
-
|
|
196
|
-
`
|
|
176
|
+
`DragGesture.Value.velocity` (iOS 17 and later) reports points per second as a
|
|
177
|
+
`CGSize`. A sheet-like panel that dismisses on a long drag or a fast flick:
|
|
197
178
|
|
|
198
179
|
```swift
|
|
199
|
-
struct
|
|
200
|
-
@
|
|
201
|
-
@State private var
|
|
180
|
+
struct FlickToClosePanel: View {
|
|
181
|
+
@Binding var isShown: Bool
|
|
182
|
+
@State private var pull: CGFloat = 0
|
|
202
183
|
|
|
203
|
-
private let
|
|
204
|
-
private let
|
|
184
|
+
private let closeDistance: CGFloat = 200
|
|
185
|
+
private let closeSpeed: CGFloat = 800
|
|
205
186
|
|
|
206
187
|
var body: some View {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if distance > dismissThreshold
|
|
223
|
-
|| velocity > velocityThreshold
|
|
224
|
-
{
|
|
225
|
-
withAnimation(.spring) {
|
|
226
|
-
isDismissed = true
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
withAnimation(.spring) {
|
|
230
|
-
offset = .zero
|
|
231
|
-
}
|
|
232
|
-
}
|
|
188
|
+
let panel = RoundedRectangle(cornerRadius: 24)
|
|
189
|
+
return panel
|
|
190
|
+
.fill(.background)
|
|
191
|
+
.frame(height: 360)
|
|
192
|
+
.offset(y: pull)
|
|
193
|
+
.opacity(1 - min(pull / closeDistance, 1) * 0.5)
|
|
194
|
+
.gesture(
|
|
195
|
+
DragGesture()
|
|
196
|
+
.onChanged { drag in pull = max(drag.translation.height, 0) }
|
|
197
|
+
.onEnded { drag in
|
|
198
|
+
if pull > closeDistance || drag.velocity.height > closeSpeed {
|
|
199
|
+
isShown = false
|
|
200
|
+
} else {
|
|
201
|
+
withAnimation(.spring) { pull = 0 }
|
|
233
202
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private var opacity: Double {
|
|
239
|
-
let progress = min(offset.height / dismissThreshold, 1.0)
|
|
240
|
-
return 1.0 - (progress * 0.5)
|
|
203
|
+
}
|
|
204
|
+
)
|
|
241
205
|
}
|
|
242
206
|
}
|
|
243
207
|
```
|
|
244
208
|
|
|
245
|
-
|
|
209
|
+
Only downward movement counts; upward drags clamp to 0. The panel fades toward
|
|
210
|
+
half opacity as it approaches the threshold.
|
|
211
|
+
|
|
212
|
+
Before iOS 17 there is no `velocity`. Estimate momentum from how far the
|
|
213
|
+
system predicts the drag would coast:
|
|
246
214
|
|
|
247
215
|
```swift
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
width: value.predictedEndTranslation.width - value.translation.width,
|
|
253
|
-
height: value.predictedEndTranslation.height - value.translation.height
|
|
254
|
-
)
|
|
255
|
-
let speed = sqrt(
|
|
256
|
-
predictedDelta.width * predictedDelta.width
|
|
257
|
-
+ predictedDelta.height * predictedDelta.height
|
|
258
|
-
)
|
|
259
|
-
if speed > 500 { handleFlick() }
|
|
260
|
-
}
|
|
216
|
+
let coastX = drag.predictedEndTranslation.width - drag.translation.width
|
|
217
|
+
let coastY = drag.predictedEndTranslation.height - drag.translation.height
|
|
218
|
+
let momentum = (coastX * coastX + coastY * coastY).squareRoot()
|
|
219
|
+
let isFlick = momentum > 500
|
|
261
220
|
```
|
|
262
221
|
|
|
263
|
-
## Long-
|
|
222
|
+
## Long-press then drag
|
|
264
223
|
|
|
265
|
-
Model
|
|
224
|
+
Model the combined gesture as an explicit state machine held in `@GestureState`.
|
|
266
225
|
|
|
267
226
|
```swift
|
|
268
|
-
enum
|
|
269
|
-
case
|
|
270
|
-
case
|
|
271
|
-
case
|
|
272
|
-
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
case .dragging(let t): return t
|
|
277
|
-
}
|
|
227
|
+
enum PressDrag {
|
|
228
|
+
case idle
|
|
229
|
+
case armed
|
|
230
|
+
case moving(CGSize)
|
|
231
|
+
|
|
232
|
+
var offset: CGSize {
|
|
233
|
+
if case .moving(let amount) = self { return amount }
|
|
234
|
+
return .zero
|
|
278
235
|
}
|
|
279
236
|
|
|
280
|
-
var
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
case .pressing, .dragging: return true
|
|
284
|
-
}
|
|
237
|
+
var engaged: Bool {
|
|
238
|
+
if case .idle = self { return false }
|
|
239
|
+
return true
|
|
285
240
|
}
|
|
286
241
|
}
|
|
287
242
|
|
|
288
|
-
struct
|
|
289
|
-
@GestureState private var
|
|
290
|
-
@State private var
|
|
243
|
+
struct PressThenDragChip: View {
|
|
244
|
+
@GestureState private var phase = PressDrag.idle
|
|
245
|
+
@State private var spot = CGPoint(x: 150, y: 150)
|
|
291
246
|
|
|
292
247
|
var body: some View {
|
|
293
|
-
Circle()
|
|
294
|
-
|
|
295
|
-
.
|
|
296
|
-
.
|
|
297
|
-
.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
)
|
|
301
|
-
.animation(.spring, value: dragState.isActive)
|
|
248
|
+
let chip = Circle()
|
|
249
|
+
return chip
|
|
250
|
+
.fill(phase.engaged ? .orange : .blue)
|
|
251
|
+
.frame(width: 70, height: 70)
|
|
252
|
+
.scaleEffect(phase.engaged ? 1.2 : 1)
|
|
253
|
+
.position(x: spot.x + phase.offset.width, y: spot.y + phase.offset.height)
|
|
254
|
+
.animation(.spring, value: phase.engaged)
|
|
302
255
|
.gesture(
|
|
303
|
-
LongPressGesture(minimumDuration: 0.
|
|
256
|
+
LongPressGesture(minimumDuration: 0.6)
|
|
304
257
|
.sequenced(before: DragGesture())
|
|
305
|
-
.updating($
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
default:
|
|
314
|
-
state = .inactive
|
|
258
|
+
.updating($phase) { step, machine, _ in
|
|
259
|
+
if case .second(true, let drag) = step {
|
|
260
|
+
machine = .moving(drag?.translation ?? .zero)
|
|
261
|
+
} else if case .first(true) = step {
|
|
262
|
+
machine = .armed
|
|
263
|
+
} else {
|
|
264
|
+
machine = .idle
|
|
315
265
|
}
|
|
316
266
|
}
|
|
317
|
-
.onEnded {
|
|
318
|
-
guard case .second(true, let drag?) =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
position.width += drag.translation.width
|
|
322
|
-
position.height += drag.translation.height
|
|
267
|
+
.onEnded { outcome in
|
|
268
|
+
guard case .second(true, let drag?) = outcome else { return }
|
|
269
|
+
spot.x += drag.translation.width
|
|
270
|
+
spot.y += drag.translation.height
|
|
323
271
|
}
|
|
324
272
|
)
|
|
325
273
|
}
|
|
326
274
|
}
|
|
327
275
|
```
|
|
328
276
|
|
|
329
|
-
## SwiftUI
|
|
277
|
+
## SwiftUI and UIKit gesture interop
|
|
330
278
|
|
|
331
|
-
###
|
|
279
|
+
### A UIKit recognizer inside SwiftUI (iOS 18+)
|
|
332
280
|
|
|
333
|
-
|
|
281
|
+
`UIGestureRecognizerRepresentable` brings any `UIGestureRecognizer` into a
|
|
282
|
+
SwiftUI view and is attached with `.gesture(_:)`.
|
|
334
283
|
|
|
335
284
|
```swift
|
|
336
|
-
struct
|
|
337
|
-
|
|
285
|
+
struct IncrementalPinch: UIGestureRecognizerRepresentable {
|
|
286
|
+
var onStep: (CGFloat) -> Void
|
|
338
287
|
|
|
339
288
|
func makeUIGestureRecognizer(context: Context) -> UIPinchGestureRecognizer {
|
|
340
289
|
UIPinchGestureRecognizer()
|
|
341
290
|
}
|
|
342
291
|
|
|
343
|
-
func handleUIGestureRecognizerAction(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
switch recognizer.state {
|
|
348
|
-
case .changed:
|
|
349
|
-
scale = recognizer.scale
|
|
350
|
-
case .ended:
|
|
351
|
-
scale = recognizer.scale
|
|
352
|
-
recognizer.scale = 1.0
|
|
353
|
-
default:
|
|
354
|
-
break
|
|
292
|
+
func handleUIGestureRecognizerAction(_ recognizer: UIPinchGestureRecognizer, context: Context) {
|
|
293
|
+
if recognizer.state == .changed { onStep(recognizer.scale) }
|
|
294
|
+
if recognizer.state == .changed || recognizer.state == .ended {
|
|
295
|
+
recognizer.scale = 1 // report each step, not the running total
|
|
355
296
|
}
|
|
356
297
|
}
|
|
357
298
|
}
|
|
358
299
|
|
|
359
|
-
// Usage
|
|
360
|
-
|
|
361
|
-
@State private var scale: CGFloat = 1.0
|
|
362
|
-
|
|
363
|
-
var body: some View {
|
|
364
|
-
Image("photo")
|
|
365
|
-
.scaleEffect(scale)
|
|
366
|
-
.gesture(PinchGestureView(scale: $scale))
|
|
367
|
-
}
|
|
368
|
-
}
|
|
300
|
+
// Usage
|
|
301
|
+
mapLayer.gesture(IncrementalPinch { step in zoom *= step })
|
|
369
302
|
```
|
|
370
303
|
|
|
371
|
-
###
|
|
304
|
+
### SwiftUI gestures inside UIKit
|
|
305
|
+
|
|
306
|
+
Host the SwiftUI view with `UIHostingController`; its gestures work unchanged.
|
|
372
307
|
|
|
373
308
|
```swift
|
|
374
|
-
class
|
|
309
|
+
final class CanvasViewController: UIViewController {
|
|
310
|
+
private let planName = "floorplan"
|
|
311
|
+
|
|
375
312
|
override func viewDidLoad() {
|
|
376
313
|
super.viewDidLoad()
|
|
377
|
-
|
|
378
|
-
let
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
view.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
struct InteractiveCard: View {
|
|
388
|
-
@GestureState private var dragOffset = CGSize.zero
|
|
389
|
-
|
|
390
|
-
var body: some View {
|
|
391
|
-
RoundedRectangle(cornerRadius: 16)
|
|
392
|
-
.fill(.blue.gradient)
|
|
393
|
-
.frame(width: 200, height: 300)
|
|
394
|
-
.offset(dragOffset)
|
|
395
|
-
.gesture(
|
|
396
|
-
DragGesture()
|
|
397
|
-
.updating($dragOffset) { value, state, _ in
|
|
398
|
-
state = value.translation
|
|
399
|
-
}
|
|
400
|
-
)
|
|
314
|
+
let content = ZoomableImage(image: Image(planName))
|
|
315
|
+
let host = UIHostingController(rootView: content)
|
|
316
|
+
addChild(host)
|
|
317
|
+
view.addSubview(host.view)
|
|
318
|
+
host.view.frame = view.bounds
|
|
319
|
+
host.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
320
|
+
host.didMove(toParent: self)
|
|
401
321
|
}
|
|
402
322
|
}
|
|
403
323
|
```
|
|
404
324
|
|
|
405
|
-
###
|
|
325
|
+
### Letting both sides recognize
|
|
406
326
|
|
|
407
|
-
When
|
|
408
|
-
|
|
327
|
+
When a SwiftUI gesture and a UIKit recognizer overlap, allow both on each side:
|
|
328
|
+
use `.simultaneousGesture` in SwiftUI, and have the UIKit delegate agree to run
|
|
329
|
+
together.
|
|
409
330
|
|
|
410
331
|
```swift
|
|
411
|
-
|
|
412
|
-
func gestureRecognizer(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
) -> Bool {
|
|
416
|
-
|
|
332
|
+
extension CanvasViewController: UIGestureRecognizerDelegate {
|
|
333
|
+
func gestureRecognizer(
|
|
334
|
+
_ recognizer: UIGestureRecognizer,
|
|
335
|
+
shouldRecognizeSimultaneouslyWith partner: UIGestureRecognizer
|
|
336
|
+
) -> Bool {
|
|
337
|
+
true
|
|
338
|
+
}
|
|
417
339
|
}
|
|
418
340
|
```
|
|
419
341
|
|
|
420
|
-
## Accessibility
|
|
342
|
+
## Accessibility considerations
|
|
421
343
|
|
|
422
|
-
|
|
344
|
+
Every gesture-only interaction needs an alternative for VoiceOver, Switch
|
|
345
|
+
Control and keyboard users. Expose the same effect as accessibility actions:
|
|
423
346
|
|
|
424
347
|
```swift
|
|
425
|
-
|
|
426
|
-
.
|
|
427
|
-
.
|
|
428
|
-
.accessibilityAction(
|
|
429
|
-
.accessibilityAction(named: "Move
|
|
430
|
-
withAnimation { offset.height -= 50 }
|
|
431
|
-
}
|
|
432
|
-
.accessibilityAction(named: "Move down") {
|
|
433
|
-
withAnimation { offset.height += 50 }
|
|
434
|
-
}
|
|
348
|
+
Text(stop)
|
|
349
|
+
.gesture(reorder(stop))
|
|
350
|
+
.accessibilityAction(.default) { openDetails(for: stop) }
|
|
351
|
+
.accessibilityAction(named: "Move earlier") { shift(stop, by: -1) }
|
|
352
|
+
.accessibilityAction(named: "Move later") { shift(stop, by: 1) }
|
|
435
353
|
```
|