@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,169 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sheet Routing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Part of the `swiftui-navigation` skill. Routing every modal through one place
|
|
4
|
+
lets any view open a sheet without threading bindings through its parents, and
|
|
5
|
+
it keeps working as screens are added.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- [Core architecture](#core-architecture)
|
|
7
|
-
- [Example: SheetDestination enum](#example-sheetdestination-enum)
|
|
8
|
-
- [Example: withSheetDestinations modifier](#example-withsheetdestinations-modifier)
|
|
9
|
-
- [Example: presenting from a child view](#example-presenting-from-a-child-view)
|
|
10
|
-
- [Required wiring](#required-wiring)
|
|
11
|
-
- [Example: sheets that need their own navigation](#example-sheets-that-need-their-own-navigation)
|
|
12
|
-
- [Design choices to keep](#design-choices-to-keep)
|
|
13
|
-
- [iOS 26 Presentation Sizing](#ios-26-presentation-sizing)
|
|
14
|
-
- [Pitfalls](#pitfalls)
|
|
7
|
+
## Building blocks
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
- A `SheetDestination` enum, `Identifiable`, with one case per modal.
|
|
10
|
+
- `presentedSheet: SheetDestination?` stored on the router.
|
|
11
|
+
- A modifier, here `withSheetDestinations(sheet:)`, that turns a case into its
|
|
12
|
+
view.
|
|
13
|
+
- The router in the environment, so children assign `presentedSheet` directly.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## Core architecture
|
|
21
|
-
|
|
22
|
-
- Define a `SheetDestination` enum that describes every modal and is `Identifiable`.
|
|
23
|
-
- Store the current sheet in a router object (`presentedSheet: SheetDestination?`).
|
|
24
|
-
- Create a view modifier like `withSheetDestinations(...)` that maps the enum to concrete sheet views.
|
|
25
|
-
- Inject the router into the environment so child views can set `presentedSheet` directly.
|
|
26
|
-
|
|
27
|
-
## Example: SheetDestination enum
|
|
15
|
+
## The destination enum
|
|
28
16
|
|
|
29
17
|
```swift
|
|
30
|
-
enum SheetDestination:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return "editor"
|
|
41
|
-
case .settings:
|
|
42
|
-
return "settings"
|
|
43
|
-
case .report:
|
|
44
|
-
return "report"
|
|
18
|
+
enum SheetDestination: Hashable, Identifiable {
|
|
19
|
+
case newTrip
|
|
20
|
+
case editTrip(Trip.ID)
|
|
21
|
+
case sharePhoto(Photo.ID)
|
|
22
|
+
case report(itemID: String)
|
|
23
|
+
|
|
24
|
+
var id: String {
|
|
25
|
+
if case .sharePhoto = self { return "share" }
|
|
26
|
+
if case .report = self { return "report" }
|
|
27
|
+
return "trip-editor"
|
|
45
28
|
}
|
|
46
|
-
}
|
|
47
29
|
}
|
|
48
30
|
```
|
|
49
31
|
|
|
50
|
-
|
|
32
|
+
`newTrip` and `editTrip` return the same `id`. They are two ways into one
|
|
33
|
+
editor, so they can never be on screen together.
|
|
34
|
+
|
|
35
|
+
## The mapping modifier
|
|
51
36
|
|
|
52
37
|
```swift
|
|
53
38
|
extension View {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
case .settings:
|
|
65
|
-
SettingsView()
|
|
66
|
-
case .report(let itemID):
|
|
67
|
-
ReportView(itemID: itemID)
|
|
39
|
+
func withSheetDestinations(sheet: Binding<SheetDestination?>) -> some View {
|
|
40
|
+
self.sheet(item: sheet) { modal in
|
|
41
|
+
Group {
|
|
42
|
+
switch modal {
|
|
43
|
+
case .newTrip: TripEditor(trip: nil)
|
|
44
|
+
case .editTrip(let id): TripEditor(tripID: id)
|
|
45
|
+
case .sharePhoto(let id): PhotoShareView(photoID: id)
|
|
46
|
+
case .report(let itemID): ReportForm(itemID: itemID)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
68
49
|
}
|
|
69
|
-
}
|
|
70
50
|
}
|
|
71
|
-
}
|
|
72
51
|
}
|
|
73
52
|
```
|
|
74
53
|
|
|
75
|
-
##
|
|
54
|
+
## Opening a sheet from deep in the tree
|
|
76
55
|
|
|
77
56
|
```swift
|
|
78
|
-
struct
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
57
|
+
struct CommentRow: View {
|
|
58
|
+
@Environment(Router.self) private var router
|
|
59
|
+
let comment: Comment
|
|
60
|
+
|
|
61
|
+
var body: some View {
|
|
62
|
+
Text(comment.body)
|
|
63
|
+
.contextMenu {
|
|
64
|
+
Button("Report", systemImage: "flag") {
|
|
65
|
+
router.presentedSheet = .report(itemID: comment.id)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
84
68
|
}
|
|
85
|
-
}
|
|
86
69
|
}
|
|
87
70
|
```
|
|
88
71
|
|
|
89
|
-
##
|
|
72
|
+
## Wiring at the root
|
|
73
|
+
|
|
74
|
+
1. The parent owns the router instance.
|
|
75
|
+
2. It installs `withSheetDestinations(sheet: $router.presentedSheet)`; a plain
|
|
76
|
+
`sheet(item:)` does the same job.
|
|
77
|
+
3. `.environment(router)` comes later in the chain than the sheet modifier, so
|
|
78
|
+
whatever the sheet shows can read the router as well.
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
```swift
|
|
81
|
+
struct TripsRoot: View {
|
|
82
|
+
@State private var router = Router()
|
|
83
|
+
|
|
84
|
+
var body: some View {
|
|
85
|
+
NavigationStack(path: $router.path) { TripsList().withAppRouter() }
|
|
86
|
+
.withSheetDestinations(sheet: $router.presentedSheet)
|
|
87
|
+
.environment(router)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
With that in place, a child that sets `presentedSheet` makes the root present
|
|
93
|
+
the sheet.
|
|
97
94
|
|
|
98
|
-
##
|
|
95
|
+
## Sheets that push their own screens
|
|
99
96
|
|
|
100
|
-
Wrap
|
|
97
|
+
Wrap the content in a stack with a close button when a sheet needs its own
|
|
98
|
+
navigation.
|
|
101
99
|
|
|
102
100
|
```swift
|
|
103
101
|
struct NavigationSheet<Content: View>: View {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
@ViewBuilder var content: Content
|
|
103
|
+
@Environment(\.dismiss) private var close
|
|
104
|
+
|
|
105
|
+
var body: some View {
|
|
106
|
+
NavigationStack {
|
|
107
|
+
content
|
|
108
|
+
.toolbar {
|
|
109
|
+
ToolbarItem(placement: .cancellationAction) {
|
|
110
|
+
Button("Close", systemImage: "xmark") { close() }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
110
114
|
}
|
|
111
|
-
}
|
|
112
115
|
}
|
|
113
116
|
```
|
|
114
117
|
|
|
115
|
-
##
|
|
118
|
+
## Rules worth keeping
|
|
116
119
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
120
|
+
- Route sheets centrally so features do not pass bindings through layers.
|
|
121
|
+
- `sheet(item:)` keeps exactly one sheet active, chosen by the enum.
|
|
122
|
+
- Mutually exclusive sheets share an `id`.
|
|
123
|
+
- Sheet views stay small and composed from pieces.
|
|
121
124
|
|
|
122
|
-
##
|
|
125
|
+
## Presentation sizing
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
The section concerns iOS 26 layouts, but `presentationSizing(_:)` itself is
|
|
128
|
+
available from iOS 18. Values: `.automatic`, `.page`, `.form`, `.fitted`.
|
|
129
|
+
`.fitted(horizontal:vertical:)` fits only the axes you pass, and
|
|
130
|
+
`.sticky(horizontal:vertical:)` lets the sheet grow along them without
|
|
131
|
+
shrinking.
|
|
125
132
|
|
|
126
133
|
```swift
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.presentationSizing(.form)
|
|
130
|
-
}
|
|
134
|
+
TripEditor(trip: nil)
|
|
135
|
+
.presentationSizing(.form.sticky(horizontal: false, vertical: true))
|
|
131
136
|
```
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
- `.automatic` -- platform default
|
|
135
|
-
- `.page` -- roughly paper size, for informational content
|
|
136
|
-
- `.form` -- slightly narrower than page, for form-style UI
|
|
137
|
-
- `.fitted` -- sized by the content's ideal size
|
|
138
|
+
## Confirming dismissal
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
- `.sticky(horizontal:vertical:)` -- grow but do not shrink in specified dimensions
|
|
142
|
-
|
|
143
|
-
### Dismissal Confirmation (macOS 15+ / iOS 26+)
|
|
144
|
-
|
|
145
|
-
Show a confirmation dialog when the user tries to dismiss a sheet with unsaved changes:
|
|
140
|
+
On iOS, block the swipe with `.interactiveDismissDisabled(_:)` while there are
|
|
141
|
+
unsaved edits, and ask from your own Close button with `confirmationDialog`:
|
|
146
142
|
|
|
147
143
|
```swift
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
struct TripEditorSheet: View {
|
|
145
|
+
let id: Trip.ID
|
|
146
|
+
let hasEdits: Bool
|
|
147
|
+
let draft: Draft
|
|
148
|
+
@State private var confirmsDiscard = false
|
|
149
|
+
@Environment(\.dismiss) private var dismiss
|
|
150
|
+
|
|
151
|
+
var body: some View {
|
|
152
|
+
NavigationStack {
|
|
153
|
+
TripEditor(tripID: id)
|
|
154
|
+
.toolbar {
|
|
155
|
+
ToolbarItem(placement: .cancellationAction) {
|
|
156
|
+
Button("Close", systemImage: "xmark") {
|
|
157
|
+
if hasEdits { confirmsDiscard = true } else { dismiss() }
|
|
158
|
+
}
|
|
159
|
+
.confirmationDialog("Throw away your edits?", isPresented: $confirmsDiscard) {
|
|
160
|
+
Button("Throw Away", role: .destructive) {
|
|
161
|
+
draft.revert()
|
|
162
|
+
dismiss()
|
|
163
|
+
}
|
|
164
|
+
Button("Save and Close") {
|
|
165
|
+
draft.save()
|
|
166
|
+
dismiss()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
}
|
|
172
|
+
.interactiveDismissDisabled(hasEdits)
|
|
173
|
+
}
|
|
156
174
|
}
|
|
157
175
|
```
|
|
158
176
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
On macOS 15 and later, `dismissalConfirmationDialog(_:shouldPresent:)` does
|
|
178
|
+
this for you. The SDK marks it unavailable on iOS, iPadOS, tvOS, watchOS and
|
|
179
|
+
visionOS, so it only compiles in a macOS target:
|
|
180
|
+
|
|
181
|
+
```swift
|
|
182
|
+
TripEditor(tripID: id)
|
|
183
|
+
.dismissalConfirmationDialog("Throw away your edits?", shouldPresent: hasEdits) {
|
|
184
|
+
Button("Throw Away", role: .destructive) { draft.revert() }
|
|
185
|
+
Button("Save and Close") { draft.save() }
|
|
186
|
+
.keyboardShortcut(.defaultAction)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The system adds Cancel on its own, and Cancel keeps the sheet open. Any other
|
|
191
|
+
button lets the dismissal go ahead. `.keyboardShortcut(.defaultAction)` marks
|
|
192
|
+
the default choice.
|
|
162
193
|
|
|
163
194
|
## Pitfalls
|
|
164
195
|
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
-
|
|
196
|
+
- Two presentation styles, `sheet(isPresented:)` plus `sheet(item:)`, for one
|
|
197
|
+
concern. A single enum is enough.
|
|
198
|
+
- Carrying heavy state inside `SheetDestination`. Pass identifiers or small
|
|
199
|
+
models.
|
|
200
|
+
- Reusing an `id` for sheets that may legitimately come from the same screen
|
|
201
|
+
one after another; give them distinct values.
|
|
202
|
+
- Fixed frames instead of `presentationSizing(.form)`.
|
|
203
|
+
- A dismissal dialog without a real `shouldPresent` condition. Asking when
|
|
204
|
+
nothing changed only confuses people.
|
|
@@ -1,178 +1,144 @@
|
|
|
1
|
-
# TabView
|
|
1
|
+
# TabView Architecture
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Part of the `swiftui-navigation` skill. Goal: tabs that scale across platforms,
|
|
4
|
+
with one source of truth for what each tab is and shows, different tab sets and
|
|
5
|
+
sidebar groupings per platform, tabs generated from data, and a hook that can
|
|
6
|
+
intercept special tabs.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
- [Core architecture](#core-architecture)
|
|
7
|
-
- [Example: custom binding with side effects](#example-custom-binding-with-side-effects)
|
|
8
|
-
- [Example: direct binding without side effects](#example-direct-binding-without-side-effects)
|
|
9
|
-
- [Design choices to keep](#design-choices-to-keep)
|
|
10
|
-
- [Dynamic tabs pattern](#dynamic-tabs-pattern)
|
|
11
|
-
- [iOS 26 Tab API](#ios-26-tab-api)
|
|
12
|
-
- [Pitfalls](#pitfalls)
|
|
8
|
+
## Building blocks
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
- `AppTab` enum: identity, title, icon and a builder for its content.
|
|
11
|
+
- `SidebarSections` enum: which tabs appear under which sidebar heading. Give
|
|
12
|
+
its `title` the type `LocalizedStringKey`. With a `String` title,
|
|
13
|
+
`TabSection(group.title)` makes `Tab(value:)` ambiguous and the view does not
|
|
14
|
+
compile.
|
|
15
|
+
- `AppView`: owns the `TabView` and the selection, and routes selection changes
|
|
16
|
+
through `updateTab(with:)`.
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
- a single source of truth for tab identity and content,
|
|
18
|
-
- platform-specific tab sets and sidebar sections,
|
|
19
|
-
- dynamic tabs sourced from data,
|
|
20
|
-
- an interception hook for special tabs (e.g., compose).
|
|
21
|
-
|
|
22
|
-
## Core architecture
|
|
23
|
-
|
|
24
|
-
- `AppTab` enum defines identity, labels, icons, and content builder.
|
|
25
|
-
- `SidebarSections` enum groups tabs for sidebar sections.
|
|
26
|
-
- `AppView` owns the `TabView` and selection binding, and routes tab changes through `updateTab`.
|
|
27
|
-
|
|
28
|
-
## Example: custom binding with side effects
|
|
29
|
-
|
|
30
|
-
Use this when tab selection needs side effects, like intercepting a special tab to perform an action instead of changing selection.
|
|
18
|
+
## Selection with side effects
|
|
31
19
|
|
|
32
20
|
```swift
|
|
33
|
-
@MainActor
|
|
34
21
|
struct AppView: View {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
get: { selectedTab },
|
|
40
|
-
set: { updateTab(with: $0) }
|
|
41
|
-
)) {
|
|
42
|
-
ForEach(availableSections) { section in
|
|
43
|
-
TabSection(section.title) {
|
|
44
|
-
ForEach(section.tabs) { tab in
|
|
45
|
-
Tab(value: tab) {
|
|
46
|
-
tab.makeContentView(
|
|
47
|
-
homeTimeline: $timeline,
|
|
48
|
-
selectedTab: $selectedTab,
|
|
49
|
-
pinnedFilters: $pinnedFilters
|
|
50
|
-
)
|
|
51
|
-
} label: {
|
|
52
|
-
tab.label
|
|
53
|
-
}
|
|
54
|
-
.tabPlacement(tab.tabPlacement)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
.tabPlacement(.sidebarOnly)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
22
|
+
let sections: [SidebarSections]
|
|
23
|
+
@State private var scrollTarget: AppTab?
|
|
24
|
+
@State private var showsComposer = false
|
|
25
|
+
@State private var selectedTab: AppTab = .home
|
|
61
26
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Intercept special tabs (compose) instead of changing selection.
|
|
65
|
-
presentComposer()
|
|
66
|
-
return
|
|
27
|
+
private var selection: Binding<AppTab> {
|
|
28
|
+
Binding(get: { selectedTab }, set: updateTab(with:))
|
|
67
29
|
}
|
|
68
|
-
selectedTab = newTab
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Example: direct binding without side effects
|
|
74
30
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
homeTimeline: $timeline,
|
|
90
|
-
selectedTab: $selectedTab,
|
|
91
|
-
pinnedFilters: $pinnedFilters
|
|
92
|
-
)
|
|
93
|
-
} label: {
|
|
94
|
-
tab.label
|
|
31
|
+
var body: some View {
|
|
32
|
+
TabView(selection: selection) {
|
|
33
|
+
ForEach(sections) { group in
|
|
34
|
+
TabSection(group.title) {
|
|
35
|
+
ForEach(group.tabs) { entry in
|
|
36
|
+
Tab(value: entry) {
|
|
37
|
+
entry.makeContentView(scrollTarget: $scrollTarget)
|
|
38
|
+
} label: {
|
|
39
|
+
entry.label
|
|
40
|
+
}
|
|
41
|
+
.tabPlacement(entry.tabPlacement)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
.tabPlacement(.sidebarOnly)
|
|
95
45
|
}
|
|
96
|
-
.tabPlacement(tab.tabPlacement)
|
|
97
|
-
}
|
|
98
46
|
}
|
|
99
|
-
.
|
|
100
|
-
|
|
47
|
+
.sheet(isPresented: $showsComposer) { ComposerView() }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func updateTab(with next: AppTab) {
|
|
51
|
+
if next == .post {
|
|
52
|
+
showsComposer = true
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
if next == selectedTab {
|
|
56
|
+
scrollTarget = next
|
|
57
|
+
}
|
|
58
|
+
selectedTab = next
|
|
101
59
|
}
|
|
102
|
-
}
|
|
103
60
|
}
|
|
104
61
|
```
|
|
105
62
|
|
|
106
|
-
|
|
63
|
+
Choosing `.post` opens the composer and leaves the selection where it was.
|
|
64
|
+
Choosing the current tab again asks it to scroll to the top.
|
|
107
65
|
|
|
108
|
-
|
|
109
|
-
- Use `Tab(value:)` with `selection` binding for state-driven tab selection.
|
|
110
|
-
- Route selection changes through `updateTab` to handle special tabs and scroll-to-top behavior.
|
|
111
|
-
- Use `TabSection` + `.tabPlacement(.sidebarOnly)` for sidebar structure.
|
|
112
|
-
- Use `.tabPlacement(.pinned)` in `AppTab.tabPlacement` for a single pinned tab; this is commonly used for iOS 26 `.searchable` tab content, but can be used for any tab.
|
|
66
|
+
## Selection without side effects
|
|
113
67
|
|
|
114
|
-
|
|
68
|
+
If selection is pure state, bind directly: `TabView(selection: $selectedTab)`
|
|
69
|
+
with the same `TabSection` and `Tab` structure inside.
|
|
115
70
|
|
|
116
|
-
|
|
117
|
-
- `AppTab.anyTimelineFilter(filter:)` wraps dynamic tabs in a single enum case.
|
|
118
|
-
- The enum provides label/icon/title for dynamic tabs via the filter type.
|
|
71
|
+
## Rules worth keeping
|
|
119
72
|
|
|
120
|
-
|
|
73
|
+
- `AppTab` is the single home for identity and content, through
|
|
74
|
+
`makeContentView(...)`.
|
|
75
|
+
- Tabs use `Tab(value:)` together with a selection binding.
|
|
76
|
+
- Selection goes through `updateTab(with:)`; that one function deals with
|
|
77
|
+
compose-style tabs and with re-tapping to scroll up.
|
|
78
|
+
- Sidebar structure comes from `TabSection` plus `.tabPlacement(.sidebarOnly)`.
|
|
79
|
+
- `.tabPlacement(.pinned)` keeps one tab fixed in place. It is typical for an
|
|
80
|
+
iOS 26 `.searchable` tab but works for any tab.
|
|
121
81
|
|
|
122
|
-
|
|
82
|
+
## Tabs generated from data
|
|
123
83
|
|
|
124
|
-
|
|
84
|
+
`SidebarSections` can hold tabs that come from user data, such as saved
|
|
85
|
+
filters. Represent them with one case that carries a payload, for example
|
|
86
|
+
`AppTab.savedFilter(SavedFilter)`, and read the title and icon from the filter.
|
|
125
87
|
|
|
126
88
|
```swift
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
.tabBarMinimizeBehavior(.onScrollDown) // iPhone only
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
`TabBarMinimizeBehavior` values:
|
|
134
|
-
- `.automatic` -- determine behavior from context
|
|
135
|
-
- `.onScrollDown` -- minimize when user scrolls down (iPhone only)
|
|
136
|
-
- `.onScrollUp` -- minimize when user scrolls up (iPhone only)
|
|
137
|
-
- `.never` -- never minimize the tab bar
|
|
89
|
+
enum AppTab: Hashable, Identifiable {
|
|
90
|
+
case home, search, post, profile
|
|
91
|
+
case savedFilter(SavedFilter)
|
|
138
92
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
93
|
+
var id: String {
|
|
94
|
+
guard case .savedFilter(let filter) = self else { return String(describing: self) }
|
|
95
|
+
return "filter-\(filter.id)"
|
|
96
|
+
}
|
|
142
97
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
98
|
+
@ViewBuilder var label: some View {
|
|
99
|
+
switch self {
|
|
100
|
+
case .savedFilter(let filter): Label(filter.name, systemImage: filter.symbol)
|
|
101
|
+
case .home: Label("Start", systemImage: "house")
|
|
102
|
+
case .search: Label("Search", systemImage: "magnifyingglass")
|
|
103
|
+
case .post: Label("Post", systemImage: "square.and.pencil")
|
|
104
|
+
case .profile: Label("Profile", systemImage: "person")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
146
107
|
}
|
|
147
108
|
```
|
|
148
109
|
|
|
149
|
-
|
|
110
|
+
## iOS 26 tab API
|
|
150
111
|
|
|
151
112
|
```swift
|
|
152
113
|
TabView {
|
|
153
|
-
|
|
114
|
+
Tab("Feed", systemImage: "list.bullet") { FeedView() }
|
|
115
|
+
Tab(role: .search) { SearchView() }
|
|
154
116
|
}
|
|
155
|
-
.
|
|
156
|
-
.
|
|
157
|
-
.
|
|
117
|
+
.tabBarMinimizeBehavior(.onScrollDown)
|
|
118
|
+
.tabViewBottomAccessory { MiniPlayerBar() }
|
|
119
|
+
.tabViewSidebarHeader { AccountHeader() }
|
|
120
|
+
.tabViewSidebarFooter { StorageFooter() }
|
|
121
|
+
.tabViewSidebarBottomBar { SettingsButton() }
|
|
158
122
|
```
|
|
159
123
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.tabViewBottomAccessory { NowPlayingBar() }
|
|
169
|
-
```
|
|
124
|
+
- `TabBarMinimizeBehavior`: `.automatic` (decided by context), `.onScrollDown`,
|
|
125
|
+
`.onScrollUp` and `.never`. The two scroll behaviours apply on iPhone only.
|
|
126
|
+
- `Tab(role: .search)` replaces the bar with a search field while that tab is
|
|
127
|
+
active.
|
|
128
|
+
- Sidebar customisation: `.tabViewSidebarHeader { }`,
|
|
129
|
+
`.tabViewSidebarFooter { }` and `.tabViewSidebarBottomBar { }`.
|
|
130
|
+
- `.tabViewBottomAccessory { }` shows content just above the tab bar; its
|
|
131
|
+
placement is described by `TabViewBottomAccessoryPlacement`.
|
|
170
132
|
|
|
171
133
|
## Pitfalls
|
|
172
134
|
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
-
|
|
135
|
+
- A view model per tab. Keep state local to views or in shared `@Observable`
|
|
136
|
+
services.
|
|
137
|
+
- `@Observable` objects nested inside each other.
|
|
138
|
+
- An `AppTab.id` that changes between launches; dynamic cases must hash on a
|
|
139
|
+
stable identifier.
|
|
140
|
+
- Special tabs such as compose that change the selection.
|
|
141
|
+
- `.tabItem { }` instead of `Tab(value:)` with `TabView(selection:)`, which
|
|
142
|
+
loses typed selection.
|
|
143
|
+
- Expecting `tabBarMinimizeBehavior` to work on iPad or Mac; it does nothing
|
|
144
|
+
there.
|