@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,700 +1,618 @@
|
|
|
1
|
-
# Platform
|
|
1
|
+
# Platform and Sharing
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
- [Top Bar Overlays](#top-bar-overlays)
|
|
7
|
-
- [Title Menus](#title-menus)
|
|
8
|
-
- [Input Toolbar](#input-toolbar)
|
|
9
|
-
- [Menu Bar Commands](#menu-bar-commands)
|
|
10
|
-
- [macOS Settings](#macos-settings)
|
|
11
|
-
|
|
12
|
-
## Transferable, Drag & Drop, and ShareLink
|
|
13
|
-
|
|
14
|
-
### Intent
|
|
3
|
+
Moving data in and out of the app (`Transferable`, `ShareLink`, drag and drop,
|
|
4
|
+
the pasteboard), media previews, top bar overlays, title menus, input bars,
|
|
5
|
+
menu bar commands and the macOS Settings window.
|
|
15
6
|
|
|
16
|
-
|
|
7
|
+
## Contents
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
1. [Transferable](#1-transferable)
|
|
10
|
+
2. [ShareLink](#2-sharelink)
|
|
11
|
+
3. [Drag and Drop](#3-drag-and-drop)
|
|
12
|
+
4. [Pasteboard](#4-pasteboard)
|
|
13
|
+
5. [Transferable Recipes](#5-transferable-recipes)
|
|
14
|
+
6. [Transferable Pitfalls](#6-transferable-pitfalls)
|
|
15
|
+
7. [Media Previews and Viewer](#7-media-previews-and-viewer)
|
|
16
|
+
8. [Top Bar Overlays](#8-top-bar-overlays)
|
|
17
|
+
9. [Title Menus](#9-title-menus)
|
|
18
|
+
10. [Input Toolbar](#10-input-toolbar)
|
|
19
|
+
11. [Menu Bar Commands](#11-menu-bar-commands)
|
|
20
|
+
12. [macOS Settings](#12-macos-settings)
|
|
21
|
+
|
|
22
|
+
## 1. Transferable
|
|
23
|
+
|
|
24
|
+
`Transferable` (iOS 16+) is one protocol behind sharing, drag and drop, copy
|
|
25
|
+
and paste, and `ShareLink`. Apple docs:
|
|
26
|
+
[Transferable](https://developer.apple.com/documentation/coretransferable/transferable),
|
|
27
|
+
the [representation guide](https://developer.apple.com/documentation/coretransferable/choosing-a-transfer-representation-for-a-model-type).
|
|
28
|
+
|
|
29
|
+
A type conforms by providing
|
|
30
|
+
`static var transferRepresentation: some TransferRepresentation`.
|
|
19
31
|
|
|
20
|
-
|
|
32
|
+
```swift
|
|
33
|
+
import CoreTransferable
|
|
34
|
+
import UniformTypeIdentifiers
|
|
21
35
|
|
|
22
|
-
|
|
36
|
+
struct Recipe: Codable, Identifiable {
|
|
37
|
+
var id = UUID()
|
|
38
|
+
var name: String
|
|
39
|
+
var steps: String
|
|
40
|
+
}
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let id: UUID
|
|
27
|
-
var title: String
|
|
28
|
-
var body: String
|
|
42
|
+
extension UTType {
|
|
43
|
+
static let recipe = UTType(exportedAs: "com.example.cookbook.recipe")
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
extension
|
|
46
|
+
extension Recipe: Transferable {
|
|
32
47
|
static var transferRepresentation: some TransferRepresentation {
|
|
33
|
-
CodableRepresentation(contentType: .
|
|
34
|
-
ProxyRepresentation(exporting: \.
|
|
48
|
+
CodableRepresentation(contentType: .recipe)
|
|
49
|
+
ProxyRepresentation(exporting: \.steps)
|
|
35
50
|
}
|
|
36
51
|
}
|
|
37
|
-
|
|
38
|
-
extension UTType {
|
|
39
|
-
static let note = UTType(exportedAs: "com.example.note")
|
|
40
|
-
}
|
|
41
52
|
```
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
Order matters. The receiver takes the first representation it understands, so
|
|
55
|
+
list the richest format first and broader fallbacks (plain text here) after it.
|
|
44
56
|
|
|
45
|
-
###
|
|
57
|
+
### Types that already conform
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| Type | Content type |
|
|
50
|
-
|------|-------------|
|
|
59
|
+
| Type | Content types |
|
|
60
|
+
|---|---|
|
|
51
61
|
| `String` | `.plainText`, `.utf8PlainText` |
|
|
52
62
|
| `Data` | `.data` |
|
|
53
63
|
| `URL` | `.url` |
|
|
54
|
-
| `AttributedString` |
|
|
55
|
-
| `Image`
|
|
56
|
-
| `Color`
|
|
57
|
-
|
|
58
|
-
### TransferRepresentation types
|
|
64
|
+
| `AttributedString` | rich text |
|
|
65
|
+
| SwiftUI `Image` | `.image` |
|
|
66
|
+
| SwiftUI `Color` | `.color` |
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
### Representation kinds
|
|
61
69
|
|
|
62
|
-
|
|
70
|
+
- `CodableRepresentation(contentType:)` for `Codable` types. It encodes as
|
|
71
|
+
JSON unless you pass another encoder.
|
|
72
|
+
- `ProxyRepresentation` hands off to another `Transferable` type. Good for a
|
|
73
|
+
quick text or URL fallback. `ProxyRepresentation(exporting: \.name)` only
|
|
74
|
+
exports; `ProxyRepresentation(exporting: \.link, importing: { Bookmark(link: $0) })`
|
|
75
|
+
works in both directions through a URL.
|
|
76
|
+
- `DataRepresentation(contentType:exporting:importing:)` gives full control of
|
|
77
|
+
the bytes:
|
|
63
78
|
|
|
64
79
|
```swift
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```swift
|
|
75
|
-
ProxyRepresentation(exporting: \.title) // export only
|
|
76
|
-
ProxyRepresentation(\.url) // import + export via URL
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
#### DataRepresentation
|
|
80
|
-
|
|
81
|
-
Full control over binary serialization:
|
|
82
|
-
|
|
83
|
-
```swift
|
|
84
|
-
DataRepresentation(contentType: .png) { image in
|
|
85
|
-
try image.pngData()
|
|
86
|
-
} importing: { data in
|
|
87
|
-
try MyImage(data: data)
|
|
80
|
+
extension Sticker: Transferable {
|
|
81
|
+
static var transferRepresentation: some TransferRepresentation {
|
|
82
|
+
DataRepresentation(contentType: .png) { sticker in
|
|
83
|
+
try sticker.encodedPNG()
|
|
84
|
+
} importing: { data in
|
|
85
|
+
try Sticker(pngData: data)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
For large content best transferred as files:
|
|
91
|
+
- `DataRepresentation(exportedContentType:exporting:)` is the export-only
|
|
92
|
+
version.
|
|
93
|
+
- `FileRepresentation(contentType:exporting:importing:)` moves large content as
|
|
94
|
+
files. The received file is temporary, so copy it before using it:
|
|
96
95
|
|
|
97
96
|
```swift
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
extension Recording: Transferable {
|
|
98
|
+
static var transferRepresentation: some TransferRepresentation {
|
|
99
|
+
FileRepresentation(contentType: .mpeg4Audio) { recording in
|
|
100
|
+
SentTransferredFile(recording.fileURL)
|
|
101
|
+
} importing: { received in
|
|
102
|
+
let target = FileManager.default.temporaryDirectory
|
|
103
|
+
.appending(path: received.file.lastPathComponent)
|
|
104
|
+
let files = FileManager.default
|
|
105
|
+
try? files.removeItem(at: target)
|
|
106
|
+
try files.copyItem(at: received.file, to: target)
|
|
107
|
+
return Recording(fileURL: target)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
104
110
|
}
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
## 2. ShareLink
|
|
108
114
|
|
|
109
|
-
|
|
115
|
+
`ShareLink` presents the system share sheet. Its item must be `Transferable`.
|
|
116
|
+
`SharePreview` supplies a title and, optionally, an image and an icon.
|
|
110
117
|
|
|
111
118
|
```swift
|
|
112
|
-
ShareLink(item:
|
|
113
|
-
Label("Share", systemImage: "square.and.arrow.up")
|
|
119
|
+
ShareLink(item: recipe, preview: SharePreview(recipe.name)) {
|
|
120
|
+
Label("Share recipe", systemImage: "square.and.arrow.up")
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
SharePreview(note.title)
|
|
123
|
+
ShareLink(items: selectedRecipes) { recipe in
|
|
124
|
+
SharePreview(recipe.name)
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
ShareLink(item: "Check out this app!", subject: Text("Cool App"))
|
|
127
|
+
ShareLink(item: "Dinner at 8", subject: Text("Tonight"))
|
|
123
128
|
```
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
### Drag and drop
|
|
130
|
+
## 3. Drag and Drop
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
- `.draggable(value)` makes a view a drag source; the value must be
|
|
133
|
+
`Transferable`.
|
|
134
|
+
- `.draggable(value) { preview }` supplies a custom drag preview.
|
|
130
135
|
|
|
131
136
|
```swift
|
|
132
|
-
struct
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
var body: some View {
|
|
136
|
-
Text(note.title)
|
|
137
|
-
.draggable(note) // Note must be Transferable
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Use `.draggable(note) { DragPreview(note) }` to provide a custom drag preview.
|
|
143
|
-
|
|
144
|
-
#### Drop destination
|
|
145
|
-
|
|
146
|
-
```swift
|
|
147
|
-
struct NoteBoard: View {
|
|
148
|
-
@State private var notes: [Note] = []
|
|
137
|
+
struct RecipeBoard: View {
|
|
138
|
+
@State private var pinned: [Recipe] = []
|
|
139
|
+
@State private var isHovering = false
|
|
149
140
|
|
|
150
141
|
var body: some View {
|
|
151
142
|
VStack {
|
|
152
|
-
ForEach(
|
|
143
|
+
ForEach(pinned) { Text($0.name) }
|
|
153
144
|
}
|
|
154
|
-
.
|
|
155
|
-
|
|
145
|
+
.frame(maxWidth: .infinity, minHeight: 200)
|
|
146
|
+
.background(isHovering ? Color.accentColor.opacity(0.15) : Color.clear)
|
|
147
|
+
.dropDestination(for: Recipe.self) { recipes, _ in
|
|
148
|
+
pinned.append(contentsOf: recipes)
|
|
156
149
|
return true
|
|
157
|
-
} isTargeted: {
|
|
158
|
-
|
|
150
|
+
} isTargeted: { hovering in
|
|
151
|
+
isHovering = hovering
|
|
159
152
|
}
|
|
160
153
|
}
|
|
161
154
|
}
|
|
162
155
|
```
|
|
163
156
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Accept multiple content types with separate `.dropDestination` modifiers or use `DropDelegate` for advanced logic:
|
|
157
|
+
- To reorder rows, either pair `.draggable` and `.dropDestination` yourself,
|
|
158
|
+
or let a `List` do it through `onMove` on its `ForEach`.
|
|
159
|
+
- Several accepted types: add one `.dropDestination` per type, or use a
|
|
160
|
+
`DropDelegate` for more complex rules.
|
|
169
161
|
|
|
170
162
|
```swift
|
|
171
|
-
.dropDestination(for: String.self) {
|
|
172
|
-
|
|
163
|
+
.dropDestination(for: String.self) { texts, _ in
|
|
164
|
+
pinned.append(contentsOf: texts.map { Recipe(name: $0, steps: "") })
|
|
173
165
|
return true
|
|
174
166
|
}
|
|
175
167
|
```
|
|
176
168
|
|
|
177
|
-
|
|
169
|
+
## 4. Pasteboard
|
|
178
170
|
|
|
179
|
-
|
|
171
|
+
Outside drag and drop, iOS code can use `UIPasteboard` directly:
|
|
180
172
|
|
|
181
173
|
```swift
|
|
182
|
-
|
|
183
|
-
UIPasteboard.general.string
|
|
184
|
-
|
|
185
|
-
// Paste
|
|
186
|
-
if let text = UIPasteboard.general.string {
|
|
187
|
-
// use text
|
|
188
|
-
}
|
|
174
|
+
UIPasteboard.general.string = recipe.steps
|
|
175
|
+
let pasted: String? = UIPasteboard.general.string
|
|
189
176
|
```
|
|
190
177
|
|
|
191
|
-
For `Transferable`
|
|
178
|
+
For a custom `Transferable` type, export the data first:
|
|
192
179
|
|
|
193
180
|
```swift
|
|
194
|
-
let data = try await
|
|
195
|
-
UIPasteboard.general
|
|
181
|
+
let data = try await recipe.exported(as: .recipe)
|
|
182
|
+
let board = UIPasteboard.general
|
|
183
|
+
board.setData(data, forPasteboardType: UTType.recipe.identifier)
|
|
196
184
|
```
|
|
197
185
|
|
|
198
|
-
|
|
186
|
+
On iOS, `PasteButton(payloadType:onPaste:)` (iOS 16+) pastes without the
|
|
187
|
+
system permission prompt. On macOS, the SwiftUI modifiers `.copyable(_:)`,
|
|
188
|
+
`.cuttable(for:action:)` and `.pasteDestination(for:action:validator:)`
|
|
189
|
+
(macOS 13+) connect to the Edit menu and its keyboard shortcuts; prefer them
|
|
190
|
+
there over `NSPasteboard`. These three modifiers are not available on iOS.
|
|
199
191
|
|
|
200
|
-
|
|
192
|
+
## 5. Transferable Recipes
|
|
201
193
|
|
|
202
|
-
|
|
194
|
+
One enum, one plain-text representation:
|
|
203
195
|
|
|
204
196
|
```swift
|
|
205
|
-
enum
|
|
206
|
-
case
|
|
207
|
-
case
|
|
197
|
+
enum Clip: Transferable {
|
|
198
|
+
case note(String)
|
|
199
|
+
case link(URL)
|
|
208
200
|
|
|
209
201
|
static var transferRepresentation: some TransferRepresentation {
|
|
210
|
-
ProxyRepresentation {
|
|
211
|
-
switch
|
|
212
|
-
case .
|
|
213
|
-
case .
|
|
202
|
+
ProxyRepresentation { clip in
|
|
203
|
+
switch clip {
|
|
204
|
+
case .note(let text): text
|
|
205
|
+
case .link(let url): url.absoluteString
|
|
214
206
|
}
|
|
215
207
|
}
|
|
216
208
|
}
|
|
217
209
|
}
|
|
218
210
|
```
|
|
219
211
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
When your type should be sharable but not importable:
|
|
212
|
+
Export only, as a PDF:
|
|
223
213
|
|
|
224
214
|
```swift
|
|
225
|
-
extension
|
|
215
|
+
extension Statement: Transferable {
|
|
226
216
|
static var transferRepresentation: some TransferRepresentation {
|
|
227
|
-
DataRepresentation(exportedContentType: .pdf) {
|
|
228
|
-
try
|
|
217
|
+
DataRepresentation(exportedContentType: .pdf) { statement in
|
|
218
|
+
try await statement.renderPDF()
|
|
229
219
|
}
|
|
230
220
|
}
|
|
231
221
|
}
|
|
232
222
|
```
|
|
233
223
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
- Always declare custom `UTType` identifiers in Info.plist under Exported/Imported Type Identifiers.
|
|
237
|
-
- Representation order matters - the first matching representation wins. Put the richest format first.
|
|
238
|
-
- `FileRepresentation` files are temporary; copy them if you need to persist.
|
|
239
|
-
- `Transferable` conformance must be on the main type, not an extension in a different module, to avoid linker issues.
|
|
240
|
-
- Test drag and drop on device - Simulator haptics and drop targeting differ from hardware.
|
|
241
|
-
|
|
242
|
-
## Media Patterns
|
|
224
|
+
## 6. Transferable Pitfalls
|
|
243
225
|
|
|
244
|
-
|
|
226
|
+
- Every custom `UTType` needs an Info.plist entry: Exported Type Identifiers
|
|
227
|
+
for types the app defines, Imported Type Identifiers for types it only
|
|
228
|
+
reads.
|
|
229
|
+
- Files delivered through `FileRepresentation` are temporary. Copy them to keep
|
|
230
|
+
them.
|
|
231
|
+
- Declare the conformance in the module that defines the type. A conformance
|
|
232
|
+
added from another module is retroactive and can clash with one the owner
|
|
233
|
+
adds later.
|
|
234
|
+
- Test drag and drop on a device. The Simulator does not match hardware for
|
|
235
|
+
haptics and drop targeting.
|
|
245
236
|
|
|
246
|
-
|
|
237
|
+
## 7. Media Previews and Viewer
|
|
247
238
|
|
|
248
|
-
|
|
239
|
+
Goal: one consistent way to load images, show inline previews and open a
|
|
240
|
+
full-screen viewer.
|
|
249
241
|
|
|
250
|
-
-
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
|
|
242
|
+
- `AsyncImage` covers simple remote images. For caching and prefetching, a
|
|
243
|
+
library such as Nuke provides `LazyImage`.
|
|
244
|
+
- Inline media uses a light preview component.
|
|
245
|
+
- The full-screen viewer is driven by shared viewer state, for example a
|
|
246
|
+
`MediaViewer` object in the environment.
|
|
247
|
+
- On macOS and visionOS open the viewer with `openWindow`; on iOS use a sheet.
|
|
256
248
|
|
|
257
249
|
```swift
|
|
258
|
-
struct
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
250
|
+
struct AttachmentStrip: View {
|
|
251
|
+
@Environment(MediaViewer.self) private var viewer
|
|
252
|
+
let attachments: [Attachment]
|
|
253
|
+
|
|
254
|
+
var body: some View {
|
|
255
|
+
ScrollView(.horizontal, showsIndicators: false) {
|
|
256
|
+
HStack {
|
|
257
|
+
ForEach(attachments) { item in
|
|
258
|
+
Button {
|
|
259
|
+
viewer.open(item, among: attachments)
|
|
260
|
+
} label: {
|
|
261
|
+
AsyncImage(url: item.thumbnailURL) { image in
|
|
262
|
+
image.resizable().aspectRatio(contentMode: .fill)
|
|
263
|
+
} placeholder: {
|
|
264
|
+
ProgressView()
|
|
265
|
+
}
|
|
266
|
+
.frame(width: 120, height: 120)
|
|
267
|
+
.clipped()
|
|
268
|
+
}
|
|
269
|
+
.buttonStyle(.plain)
|
|
270
|
+
}
|
|
279
271
|
}
|
|
280
|
-
.frame(width: 120, height: 120)
|
|
281
|
-
.clipped()
|
|
282
|
-
}
|
|
283
|
-
.buttonStyle(.plain)
|
|
284
272
|
}
|
|
285
|
-
}
|
|
286
273
|
}
|
|
287
|
-
}
|
|
288
274
|
}
|
|
289
|
-
```
|
|
290
275
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
276
|
+
@main
|
|
277
|
+
struct AlbumApp: App {
|
|
278
|
+
@State private var viewer = MediaViewer.shared
|
|
279
|
+
|
|
280
|
+
var body: some Scene {
|
|
281
|
+
WindowGroup {
|
|
282
|
+
LibraryView()
|
|
283
|
+
.environment(viewer)
|
|
284
|
+
.sheet(item: Bindable(viewer).current) { item in
|
|
285
|
+
FullScreenMedia(item: item)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
304
289
|
}
|
|
305
290
|
```
|
|
306
291
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
- Keep previews lightweight; load full media in the viewer.
|
|
310
|
-
- Use shared viewer state so any view can open media without prop-drilling.
|
|
311
|
-
- Use a single entry point for the viewer (sheet/window) to avoid duplicates.
|
|
312
|
-
|
|
313
|
-
### Pitfalls
|
|
314
|
-
|
|
315
|
-
- Avoid loading full-size images in list rows; use resized previews.
|
|
316
|
-
- Don't present multiple viewer sheets at once; keep a single source of truth.
|
|
292
|
+
Guidelines:
|
|
317
293
|
|
|
318
|
-
|
|
294
|
+
- Keep previews light; load full-resolution media only inside the viewer.
|
|
295
|
+
- Shared viewer state lets any view open media without passing it down
|
|
296
|
+
through every layer.
|
|
297
|
+
- One entry point (a sheet or a window) avoids duplicate viewers.
|
|
319
298
|
|
|
320
|
-
|
|
299
|
+
Pitfalls:
|
|
321
300
|
|
|
322
|
-
|
|
301
|
+
- Rows that decode original-resolution images. Downscale for the preview.
|
|
302
|
+
- Two viewer sheets open together because two views each own one. One state
|
|
303
|
+
object, one presenter.
|
|
323
304
|
|
|
324
|
-
|
|
305
|
+
## 8. Top Bar Overlays
|
|
325
306
|
|
|
326
|
-
|
|
307
|
+
Goal: a custom selector or pill row above scrolling content. On iOS 26 use
|
|
308
|
+
`safeAreaBar`; earlier releases need a fallback.
|
|
327
309
|
|
|
328
310
|
```swift
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
311
|
+
struct FeedScreen: View {
|
|
312
|
+
@State private var section: FeedSection = .following
|
|
313
|
+
|
|
314
|
+
var body: some View {
|
|
315
|
+
feedList
|
|
316
|
+
.modifier(TopSelector(section: $section))
|
|
334
317
|
}
|
|
335
318
|
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Fallback for earlier iOS
|
|
339
319
|
|
|
340
|
-
|
|
320
|
+
struct TopSelector: ViewModifier {
|
|
321
|
+
@Binding var section: FeedSection
|
|
341
322
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
323
|
+
func body(content: Self.Content) -> some View {
|
|
324
|
+
if #available(iOS 26.0, *) {
|
|
325
|
+
content.safeAreaBar(edge: .top) {
|
|
326
|
+
SectionPicker(section: $section)
|
|
327
|
+
.padding(.horizontal, Spacing.medium)
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
content
|
|
331
|
+
.toolbarBackground(.hidden, for: .navigationBar)
|
|
332
|
+
.safeAreaInset(edge: .top, spacing: 0) {
|
|
333
|
+
VStack(spacing: 0) {
|
|
334
|
+
SectionPicker(section: $section)
|
|
335
|
+
.padding(.vertical, 8)
|
|
336
|
+
.background(Color.primary.opacity(0.05))
|
|
337
|
+
.background(Material.ultraThin)
|
|
338
|
+
Divider()
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
353
342
|
}
|
|
354
|
-
|
|
343
|
+
}
|
|
355
344
|
```
|
|
356
345
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
- Use `safeAreaBar` when available; it integrates better with the navigation bar.
|
|
360
|
-
- Use a subtle background + divider in the fallback to keep separation from content.
|
|
361
|
-
- Keep the selector height compact to avoid pushing content too far down.
|
|
346
|
+
Hiding the toolbar background in the fallback prevents two stacked
|
|
347
|
+
backgrounds.
|
|
362
348
|
|
|
363
|
-
|
|
349
|
+
Guidelines:
|
|
364
350
|
|
|
365
|
-
-
|
|
366
|
-
-
|
|
351
|
+
- Prefer `safeAreaBar` where available; it blends with the navigation bar.
|
|
352
|
+
- The fallback separates itself from content with a faint background and a
|
|
353
|
+
divider.
|
|
354
|
+
- Keep the selector short so content does not move far down.
|
|
367
355
|
|
|
368
|
-
|
|
356
|
+
Pitfalls:
|
|
369
357
|
|
|
370
|
-
|
|
358
|
+
- Several top insets stacked on each other add extra padding.
|
|
359
|
+
- Heavy, opaque backgrounds that clash with the navigation bar.
|
|
371
360
|
|
|
372
|
-
|
|
361
|
+
## 9. Title Menus
|
|
373
362
|
|
|
374
|
-
|
|
363
|
+
Goal: a menu on the navigation title for filtering or quick context actions,
|
|
364
|
+
with no extra chrome.
|
|
375
365
|
|
|
376
|
-
-
|
|
377
|
-
- Keep
|
|
378
|
-
|
|
379
|
-
### Example: title menu for filters
|
|
366
|
+
- `ToolbarTitleMenu` (iOS 16+) attaches a menu to the title.
|
|
367
|
+
- Keep its content short and group it with `Divider()`.
|
|
380
368
|
|
|
381
369
|
```swift
|
|
382
|
-
|
|
383
|
-
private var
|
|
384
|
-
ToolbarTitleMenu {
|
|
385
|
-
Button("Latest") { timeline = .latest }
|
|
386
|
-
Button("Resume") { timeline = .resume }
|
|
387
|
-
Divider()
|
|
388
|
-
Button("Local") { timeline = .local }
|
|
389
|
-
Button("Federated") { timeline = .federated }
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Example: attach to a view
|
|
370
|
+
struct TasksScreen: View {
|
|
371
|
+
@State private var filter: TaskFilter = .open
|
|
395
372
|
|
|
396
|
-
|
|
397
|
-
NavigationStack {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
373
|
+
var body: some View {
|
|
374
|
+
NavigationStack {
|
|
375
|
+
TaskList(filter: filter)
|
|
376
|
+
.navigationTitle(filter.title)
|
|
377
|
+
.toolbar { titleMenu }
|
|
378
|
+
}
|
|
401
379
|
}
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Example: title + menu together
|
|
406
380
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
.toolbar {
|
|
415
|
-
ToolbarItem(placement: .principal) {
|
|
416
|
-
VStack(spacing: 2) {
|
|
417
|
-
Text(timeline.title)
|
|
418
|
-
.font(.headline)
|
|
419
|
-
Text(timeline.subtitle)
|
|
420
|
-
.font(.caption)
|
|
421
|
-
.foregroundStyle(.secondary)
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
ToolbarTitleMenu {
|
|
426
|
-
Button("Home") { timeline = .home }
|
|
427
|
-
Button("Local") { timeline = .local }
|
|
428
|
-
Button("Federated") { timeline = .federated }
|
|
429
|
-
}
|
|
381
|
+
@ToolbarContentBuilder
|
|
382
|
+
private var titleMenu: some ToolbarContent {
|
|
383
|
+
ToolbarTitleMenu {
|
|
384
|
+
Button("Open") { filter = .open }
|
|
385
|
+
Button("Done") { filter = .done }
|
|
386
|
+
Divider()
|
|
387
|
+
Button("Everything") { filter = .all }
|
|
430
388
|
}
|
|
431
|
-
.navigationBarTitleDisplayMode(.inline)
|
|
432
389
|
}
|
|
433
|
-
}
|
|
434
390
|
}
|
|
435
391
|
```
|
|
436
392
|
|
|
437
|
-
|
|
393
|
+
A title with a subtitle under it:
|
|
438
394
|
|
|
439
395
|
```swift
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
396
|
+
.toolbar {
|
|
397
|
+
ToolbarItem(placement: .principal) {
|
|
398
|
+
VStack(spacing: 2) {
|
|
399
|
+
Text(project.name).font(.headline)
|
|
400
|
+
Text("\(project.openCount) open").font(.caption).foregroundStyle(.secondary)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
titleMenu
|
|
448
404
|
}
|
|
405
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
449
406
|
```
|
|
450
407
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
- Only show the title menu when filtering or context switching is available.
|
|
454
|
-
- Keep the title readable; avoid long labels that truncate.
|
|
455
|
-
- Use secondary text below the title if extra context is needed.
|
|
408
|
+
Guidelines:
|
|
456
409
|
|
|
457
|
-
|
|
410
|
+
- Show the title menu only when there is something to filter or switch.
|
|
411
|
+
- Keep the title short enough that it is not truncated.
|
|
412
|
+
- Put extra context in the secondary line under the title.
|
|
458
413
|
|
|
459
|
-
|
|
460
|
-
- Avoid using title menus for destructive actions.
|
|
414
|
+
Pitfalls:
|
|
461
415
|
|
|
462
|
-
|
|
416
|
+
- Too many options in the menu.
|
|
417
|
+
- Destructive actions in a title menu.
|
|
463
418
|
|
|
464
|
-
|
|
419
|
+
## 10. Input Toolbar
|
|
465
420
|
|
|
466
|
-
|
|
421
|
+
Goal: an input bar pinned to the bottom (chat, composer, quick actions) that
|
|
422
|
+
moves with the keyboard.
|
|
467
423
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
- Avoid embedding the input bar inside the scroll content; keep it separate.
|
|
474
|
-
|
|
475
|
-
### Example: scroll view + bottom input
|
|
424
|
+
- Anchor it with `.safeAreaInset(edge: .bottom)` so it sits above the keyboard.
|
|
425
|
+
- The messages or items scroll in a `ScrollView` or `List`.
|
|
426
|
+
- `@FocusState` decides when the field is active; focusing it on appear is
|
|
427
|
+
often right for a composer.
|
|
428
|
+
- Keep the bar out of the scrolling content.
|
|
476
429
|
|
|
477
430
|
```swift
|
|
478
431
|
@MainActor
|
|
479
|
-
struct
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
432
|
+
struct ChatThread: View {
|
|
433
|
+
@State private var draft = ""
|
|
434
|
+
@State private var position = ScrollPosition(edge: .bottom)
|
|
435
|
+
@FocusState private var isComposing: Bool
|
|
436
|
+
let messages: [Message]
|
|
437
|
+
let onSend: (String) -> Void
|
|
438
|
+
|
|
439
|
+
var body: some View {
|
|
440
|
+
ScrollView {
|
|
441
|
+
LazyVStack {
|
|
442
|
+
ForEach(messages) { MessageBubble(message: $0) }
|
|
443
|
+
}
|
|
444
|
+
.scrollTargetLayout()
|
|
445
|
+
}
|
|
446
|
+
.scrollPosition($position)
|
|
447
|
+
.scrollDismissesKeyboard(.interactively)
|
|
448
|
+
.safeAreaInset(edge: .bottom) {
|
|
449
|
+
HStack {
|
|
450
|
+
TextField("Message", text: $draft)
|
|
451
|
+
.focused($isComposing)
|
|
452
|
+
.onSubmit(send)
|
|
453
|
+
Button("Send", systemImage: "arrow.up.circle.fill", action: send)
|
|
454
|
+
}
|
|
455
|
+
.padding()
|
|
456
|
+
.background(.bar)
|
|
489
457
|
}
|
|
490
|
-
|
|
491
|
-
.scrollTargetLayout()
|
|
492
|
-
.padding(.horizontal, .layoutPadding)
|
|
458
|
+
.onAppear { isComposing = true }
|
|
493
459
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
460
|
+
|
|
461
|
+
private func send() {
|
|
462
|
+
onSend(draft)
|
|
463
|
+
draft = ""
|
|
498
464
|
}
|
|
499
|
-
.scrollDismissesKeyboard(.interactively)
|
|
500
|
-
.onAppear { isInputFocused = true }
|
|
501
|
-
}
|
|
502
465
|
}
|
|
503
466
|
```
|
|
504
467
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
- Keep the input bar visually separated from the scrollable content.
|
|
508
|
-
- Use `.scrollDismissesKeyboard(.interactively)` for chat-like screens.
|
|
509
|
-
- Ensure send actions are reachable via keyboard return or a clear button.
|
|
510
|
-
|
|
511
|
-
### Pitfalls
|
|
512
|
-
|
|
513
|
-
- Avoid placing the input view inside the scroll stack; it will jump with content.
|
|
514
|
-
- Avoid nested scroll views that fight for drag gestures.
|
|
515
|
-
|
|
516
|
-
## Menu Bar Commands
|
|
468
|
+
`ScrollPosition` and `scrollPosition(_:)` with it need iOS 18.
|
|
517
469
|
|
|
518
|
-
|
|
470
|
+
Guidelines:
|
|
519
471
|
|
|
520
|
-
-
|
|
521
|
-
-
|
|
522
|
-
-
|
|
523
|
-
- [Example: insert and replace groups](#example-insert-and-replace-groups)
|
|
524
|
-
- [Example: focused menu state](#example-focused-menu-state)
|
|
525
|
-
- [Menu bar and Settings](#menu-bar-and-settings)
|
|
526
|
-
- [Pitfalls](#pitfalls)
|
|
472
|
+
- Keep the bar visually distinct from the scrolling content.
|
|
473
|
+
- Chat-style screens use `.scrollDismissesKeyboard(.interactively)`.
|
|
474
|
+
- Sending must work from the return key and from a clear button.
|
|
527
475
|
|
|
528
|
-
|
|
476
|
+
Pitfalls:
|
|
529
477
|
|
|
530
|
-
|
|
478
|
+
- An input field placed inside the scroll stack moves with the content.
|
|
479
|
+
- Nested scroll views fighting over the drag gesture.
|
|
531
480
|
|
|
532
|
-
|
|
481
|
+
## 11. Menu Bar Commands
|
|
533
482
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
- Use `CommandMenu` for app-specific menus and group related actions.
|
|
537
|
-
- Use `CommandGroup` to insert items before/after system groups or replace them.
|
|
538
|
-
- Use `FocusedValue` for context-sensitive menu items that depend on the active scene.
|
|
483
|
+
Applies when adding to or customising the menu bar on macOS and iPadOS with
|
|
484
|
+
SwiftUI commands.
|
|
539
485
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
var body: some Scene {
|
|
546
|
-
WindowGroup {
|
|
547
|
-
ContentView()
|
|
548
|
-
}
|
|
549
|
-
.commands {
|
|
550
|
-
CommandMenu("Actions") {
|
|
551
|
-
Button("Run", action: run)
|
|
552
|
-
.keyboardShortcut("R")
|
|
553
|
-
Button("Stop", action: stop)
|
|
554
|
-
.keyboardShortcut(".")
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
private func run() {}
|
|
560
|
-
private func stop() {}
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
### Example: insert and replace groups
|
|
486
|
+
- Attach commands to a `Scene` with `.commands { }`.
|
|
487
|
+
- Add `SidebarCommands()` when the UI has a navigation sidebar.
|
|
488
|
+
- `CommandMenu` creates an app-specific menu of related actions.
|
|
489
|
+
- `CommandGroup` inserts items before or after a system group, or replaces it.
|
|
490
|
+
- `FocusedValue` makes items depend on the active scene.
|
|
565
491
|
|
|
566
492
|
```swift
|
|
567
493
|
WindowGroup {
|
|
568
|
-
|
|
494
|
+
ContentView()
|
|
569
495
|
}
|
|
570
496
|
.commands {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
497
|
+
SidebarCommands()
|
|
498
|
+
CommandMenu("Library") {
|
|
499
|
+
Button("Rescan", action: rescan).keyboardShortcut("R")
|
|
500
|
+
Button("Stop Scan", action: stopScan).keyboardShortcut(".")
|
|
501
|
+
}
|
|
502
|
+
CommandGroup(before: .systemServices) {
|
|
503
|
+
Button("Diagnostics", action: showDiagnostics)
|
|
504
|
+
}
|
|
505
|
+
CommandGroup(after: .newItem) {
|
|
506
|
+
Button("New Playlist", action: newPlaylist)
|
|
507
|
+
}
|
|
508
|
+
CommandGroup(replacing: .help) {
|
|
509
|
+
Button("Library Guide", action: openGuide)
|
|
510
|
+
}
|
|
582
511
|
}
|
|
583
512
|
```
|
|
584
513
|
|
|
585
|
-
|
|
514
|
+
Context-sensitive commands read a focused value that the active scene
|
|
515
|
+
publishes:
|
|
586
516
|
|
|
587
517
|
```swift
|
|
588
|
-
@Observable
|
|
589
|
-
final class
|
|
590
|
-
|
|
518
|
+
@MainActor @Observable
|
|
519
|
+
final class LibraryModel {
|
|
520
|
+
func addPlaylist() { }
|
|
591
521
|
}
|
|
592
522
|
|
|
593
|
-
struct
|
|
594
|
-
|
|
523
|
+
struct LibraryWindow: View {
|
|
524
|
+
@State private var model = LibraryModel()
|
|
595
525
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
526
|
+
var body: some View {
|
|
527
|
+
LibraryView(model: model)
|
|
528
|
+
.focusedSceneValue(model)
|
|
599
529
|
}
|
|
600
|
-
.focusedSceneValue(model)
|
|
601
|
-
}
|
|
602
530
|
}
|
|
603
531
|
|
|
604
|
-
struct
|
|
605
|
-
|
|
532
|
+
struct LibraryCommands: Commands {
|
|
533
|
+
@FocusedValue(LibraryModel.self) private var model: LibraryModel?
|
|
606
534
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
.disabled(model == nil)
|
|
535
|
+
var body: some Commands {
|
|
536
|
+
CommandGroup(after: .newItem) {
|
|
537
|
+
Button("Add Playlist") { model?.addPlaylist() }
|
|
538
|
+
.disabled(model == nil)
|
|
539
|
+
}
|
|
613
540
|
}
|
|
614
|
-
}
|
|
615
541
|
}
|
|
616
542
|
```
|
|
617
543
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
- If you need a custom entry point inside the app, use `OpenSettingsAction` or `SettingsLink`.
|
|
544
|
+
On macOS, the app menu gains its Settings item as soon as the `App` declares a
|
|
545
|
+
`Settings` scene. To open settings from inside the app, use `SettingsLink` or the
|
|
546
|
+
`openSettings` environment action (`OpenSettingsAction`).
|
|
622
547
|
|
|
623
|
-
|
|
548
|
+
Pitfalls:
|
|
624
549
|
|
|
625
|
-
-
|
|
626
|
-
-
|
|
550
|
+
- The same shortcut assigned in several command groups.
|
|
551
|
+
- A critical feature reachable only through a menu item.
|
|
627
552
|
|
|
628
|
-
## macOS Settings
|
|
553
|
+
## 12. macOS Settings
|
|
629
554
|
|
|
630
|
-
|
|
555
|
+
Applies when building the macOS Settings window with the SwiftUI `Settings`
|
|
556
|
+
scene.
|
|
631
557
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
-
|
|
637
|
-
-
|
|
638
|
-
- Use `TabView` to group settings sections when you have more than one category.
|
|
639
|
-
- Use `Form` inside each tab to keep controls aligned and accessible.
|
|
640
|
-
- Use `OpenSettingsAction` or `SettingsLink` for in-app entry points to the Settings window.
|
|
641
|
-
|
|
642
|
-
### Example: settings scene
|
|
558
|
+
- Declare `Settings { PreferencesView() }` in the `App`, wrapped in
|
|
559
|
+
`#if os(macOS)` so it compiles only for macOS.
|
|
560
|
+
- Give settings their own root view and back values with `@AppStorage`.
|
|
561
|
+
- More than one category: group them with a `TabView`.
|
|
562
|
+
- Use a `Form` inside each tab for aligned, accessible controls.
|
|
563
|
+
- Open settings from the app with `SettingsLink` or `OpenSettingsAction`.
|
|
643
564
|
|
|
644
565
|
```swift
|
|
645
566
|
@main
|
|
646
|
-
struct
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
Settings {
|
|
653
|
-
SettingsView()
|
|
567
|
+
struct ReaderApp: App {
|
|
568
|
+
var body: some Scene {
|
|
569
|
+
WindowGroup { LibraryView() }
|
|
570
|
+
#if os(macOS)
|
|
571
|
+
Settings { PreferencesView() }
|
|
572
|
+
#endif
|
|
654
573
|
}
|
|
655
|
-
#endif
|
|
656
|
-
}
|
|
657
574
|
}
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
### Example: tabbed settings view
|
|
661
575
|
|
|
662
|
-
```swift
|
|
663
576
|
@MainActor
|
|
664
|
-
struct
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
var body: some View {
|
|
669
|
-
TabView {
|
|
670
|
-
Tab("General", systemImage: "gear") {
|
|
671
|
-
Form {
|
|
672
|
-
Toggle("Show Previews", isOn: $showPreviews)
|
|
673
|
-
Slider(value: $fontSize, in: 9...96) {
|
|
674
|
-
Text("Font Size (\(fontSize, specifier: "%.0f") pts)")
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
577
|
+
struct PreferencesView: View {
|
|
578
|
+
@AppStorage("showThumbnails") private var showThumbnails = true
|
|
579
|
+
@AppStorage("readerTextSize") private var readerTextSize = 14.0
|
|
678
580
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
581
|
+
var body: some View {
|
|
582
|
+
TabView {
|
|
583
|
+
Tab("General", systemImage: "gearshape") {
|
|
584
|
+
Form {
|
|
585
|
+
Toggle("Show thumbnails", isOn: $showThumbnails)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
Tab("Reading", systemImage: "textformat.size") {
|
|
589
|
+
Form {
|
|
590
|
+
Slider(value: $readerTextSize, in: 9...96) {
|
|
591
|
+
Text("Text size: \(readerTextSize, specifier: "%.0f")")
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
682
595
|
}
|
|
683
|
-
|
|
596
|
+
.scenePadding()
|
|
597
|
+
.frame(maxWidth: 420, minHeight: 240)
|
|
684
598
|
}
|
|
685
|
-
.scenePadding()
|
|
686
|
-
.frame(maxWidth: 420, minHeight: 240)
|
|
687
|
-
}
|
|
688
599
|
}
|
|
689
600
|
```
|
|
690
601
|
|
|
691
|
-
|
|
602
|
+
`Tab` inside the settings `TabView` needs macOS 15; for macOS 14 use
|
|
603
|
+
`.tabItem { Label(...) }` on each child instead.
|
|
604
|
+
|
|
605
|
+
Guidelines:
|
|
692
606
|
|
|
693
|
-
-
|
|
694
|
-
|
|
695
|
-
-
|
|
607
|
+
- Do not wrap the settings view in a `NavigationStack` unless deep push
|
|
608
|
+
navigation is genuinely needed.
|
|
609
|
+
- Settings is its own window and should feel flat: tabs or sections.
|
|
610
|
+
- For hierarchical settings, use one `NavigationSplitView` with a sidebar of
|
|
611
|
+
categories.
|
|
696
612
|
|
|
697
|
-
|
|
613
|
+
Pitfalls:
|
|
698
614
|
|
|
699
|
-
-
|
|
700
|
-
|
|
615
|
+
- Reusing iOS settings layouts on the Mac (full-screen stacks, toolbar-heavy
|
|
616
|
+
flows).
|
|
617
|
+
- Deep custom hierarchies stuffed into a `Form`. Each row should hold one
|
|
618
|
+
simple, accessible control.
|