@mmerterden/multi-agent-pipeline 20.6.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 +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- 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/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- 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/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- 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/ci-cd-pipelines/SKILL.md +1 -0
- 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/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- 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/council/SKILL.md +1 -0
- 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/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- 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/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- 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/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- 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-coding-standard/SKILL.md +1 -0
- 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-module-structure/SKILL.md +1 -0
- 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-security/SKILL.md +2 -0
- 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/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- 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/nextjs-app-router/SKILL.md +1 -0
- 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/play-store-review/SKILL.md +2 -0
- 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/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- 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/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- 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/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- 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/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- 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/typescript-patterns/SKILL.md +1 -0
- 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/vue-composition/SKILL.md +1 -0
- 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/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- 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,567 +1,448 @@
|
|
|
1
|
-
# WeatherKit
|
|
1
|
+
# WeatherKit Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Larger building blocks for the `weatherkit` skill. Its SKILL.md covers setup,
|
|
4
|
+
queries, alerts, availability and the attribution rules; this file puts them
|
|
5
|
+
together.
|
|
4
6
|
|
|
5
7
|
## Contents
|
|
6
8
|
|
|
7
|
-
- [WeatherKit SwiftUI
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
9
|
+
- [WeatherKit SwiftUI integration](#weatherkit-swiftui-integration)
|
|
10
|
+
- [Attribution view](#attribution-view)
|
|
11
|
+
- [Charts integration](#charts-integration)
|
|
12
|
+
- [Historical weather statistics](#historical-weather-statistics)
|
|
13
|
+
- [Weather condition mapping](#weather-condition-mapping)
|
|
14
|
+
- [Caching strategy](#caching-strategy)
|
|
15
|
+
- [Location-based weather](#location-based-weather)
|
|
13
16
|
|
|
14
|
-
## WeatherKit SwiftUI
|
|
17
|
+
## WeatherKit SwiftUI integration
|
|
15
18
|
|
|
16
|
-
###
|
|
19
|
+
### A weather model
|
|
20
|
+
|
|
21
|
+
One `@Observable` type fetches the datasets a dashboard needs in a single call,
|
|
22
|
+
together with the attribution, and exposes loading and error state.
|
|
17
23
|
|
|
18
24
|
```swift
|
|
19
|
-
import WeatherKit
|
|
20
25
|
import CoreLocation
|
|
26
|
+
import Observation
|
|
27
|
+
import WeatherKit
|
|
21
28
|
|
|
22
|
-
@Observable
|
|
23
29
|
@MainActor
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
30
|
-
var
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
@Observable
|
|
31
|
+
final class ForecastBoard {
|
|
32
|
+
private(set) var now: CurrentWeather?
|
|
33
|
+
private(set) var hours: Forecast<HourWeather>?
|
|
34
|
+
private(set) var days: Forecast<DayWeather>?
|
|
35
|
+
private(set) var alerts: [WeatherAlert] = []
|
|
36
|
+
private(set) var credit: WeatherAttribution?
|
|
37
|
+
private(set) var busy = false
|
|
38
|
+
private(set) var failure: (any Error)?
|
|
39
|
+
|
|
40
|
+
private let source = WeatherService.shared
|
|
41
|
+
|
|
42
|
+
func load(for place: CLLocation) async {
|
|
43
|
+
busy = true
|
|
44
|
+
defer { busy = false }
|
|
39
45
|
do {
|
|
40
|
-
let (current, hourly, daily,
|
|
41
|
-
for:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.attribution = try await service.attribution
|
|
46
|
+
let (current, hourly, daily, warnings) = try await source.weather(
|
|
47
|
+
for: place, including: .current, .hourly, .daily, .alerts)
|
|
48
|
+
now = current
|
|
49
|
+
hours = hourly
|
|
50
|
+
days = daily
|
|
51
|
+
alerts = warnings ?? []
|
|
52
|
+
credit = try await source.attribution
|
|
53
|
+
failure = nil
|
|
49
54
|
} catch {
|
|
50
|
-
|
|
55
|
+
failure = error
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
isLoading = false
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
###
|
|
61
|
+
### Dashboard view
|
|
59
62
|
|
|
60
63
|
```swift
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
struct WeatherDashboardView: View {
|
|
65
|
-
@Environment(WeatherManager.self) private var manager
|
|
66
|
-
let location: CLLocation
|
|
64
|
+
struct ForecastScreen: View {
|
|
65
|
+
@State private var board = ForecastBoard()
|
|
66
|
+
let place: CLLocation
|
|
67
67
|
|
|
68
68
|
var body: some View {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if let
|
|
79
|
-
hourlyForecastSection(hourly)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if let daily = manager.dailyForecast {
|
|
83
|
-
dailyForecastSection(daily)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if let alerts = manager.alerts, !alerts.isEmpty {
|
|
87
|
-
alertsSection(alerts)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if let attribution = manager.attribution {
|
|
91
|
-
WeatherAttributionView(attribution: attribution)
|
|
92
|
-
}
|
|
69
|
+
ScrollView {
|
|
70
|
+
if board.busy && board.now == nil {
|
|
71
|
+
ProgressView().padding(.top, 80)
|
|
72
|
+
} else {
|
|
73
|
+
VStack(alignment: .leading, spacing: 24) {
|
|
74
|
+
if let now = board.now { NowPanel(now: now) }
|
|
75
|
+
if let hours = board.hours { HourStrip(hours: hours) }
|
|
76
|
+
if let days = board.days { DayList(days: days) }
|
|
77
|
+
if !board.alerts.isEmpty { AlertList(alerts: board.alerts) }
|
|
78
|
+
if let credit = board.credit { CreditFooter(credit: credit) }
|
|
93
79
|
}
|
|
94
80
|
.padding()
|
|
95
81
|
}
|
|
96
|
-
.navigationTitle("Weather")
|
|
97
|
-
.task {
|
|
98
|
-
await manager.fetchWeather(for: location)
|
|
99
|
-
}
|
|
100
|
-
.refreshable {
|
|
101
|
-
await manager.fetchWeather(for: location)
|
|
102
|
-
}
|
|
103
82
|
}
|
|
83
|
+
.task { await board.load(for: place) }
|
|
84
|
+
.refreshable { await board.load(for: place) }
|
|
104
85
|
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
105
88
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
89
|
+
The panels:
|
|
90
|
+
|
|
91
|
+
```swift
|
|
92
|
+
struct NowPanel: View {
|
|
93
|
+
let now: CurrentWeather
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.font(.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
Label(
|
|
125
|
-
"Wind \(current.wind.speed.formatted())",
|
|
126
|
-
systemImage: "wind"
|
|
127
|
-
)
|
|
128
|
-
Label(
|
|
129
|
-
"UV \(current.uvIndex.value)",
|
|
130
|
-
systemImage: "sun.max"
|
|
131
|
-
)
|
|
95
|
+
var body: some View {
|
|
96
|
+
HStack(spacing: 16) {
|
|
97
|
+
Image(systemName: now.symbolName)
|
|
98
|
+
.symbolRenderingMode(.multicolor)
|
|
99
|
+
.font(.system(size: 56))
|
|
100
|
+
VStack(alignment: .leading) {
|
|
101
|
+
Text(now.temperature.formatted()).font(.largeTitle.bold())
|
|
102
|
+
Text(now.condition.description)
|
|
103
|
+
Text("Humidity \(now.humidity.formatted(.percent)) Wind \(now.wind.speed.formatted()) UV \(now.uvIndex.value)")
|
|
104
|
+
.font(.footnote)
|
|
105
|
+
.foregroundStyle(.secondary)
|
|
132
106
|
}
|
|
133
|
-
.font(.caption)
|
|
134
107
|
}
|
|
135
|
-
.padding()
|
|
136
108
|
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
struct HourStrip: View {
|
|
112
|
+
let hours: Forecast<HourWeather>
|
|
137
113
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
VStack {
|
|
147
|
-
Text(hour.date, format: .dateTime.hour())
|
|
148
|
-
.font(.caption)
|
|
149
|
-
Image(systemName: hour.symbolName)
|
|
150
|
-
.symbolRenderingMode(.multicolor)
|
|
151
|
-
Text(hour.temperature.formatted())
|
|
152
|
-
.font(.subheadline)
|
|
153
|
-
}
|
|
114
|
+
var body: some View {
|
|
115
|
+
ScrollView(.horizontal) {
|
|
116
|
+
LazyHStack(spacing: 18) {
|
|
117
|
+
ForEach(hours.prefix(12), id: \.date) { slot in
|
|
118
|
+
VStack(spacing: 6) {
|
|
119
|
+
Text(slot.date, format: .dateTime.hour())
|
|
120
|
+
Image(systemName: slot.symbolName).symbolRenderingMode(.multicolor)
|
|
121
|
+
Text(slot.temperature.formatted(.measurement(width: .narrow)))
|
|
154
122
|
}
|
|
123
|
+
.font(.caption)
|
|
155
124
|
}
|
|
156
125
|
}
|
|
157
126
|
}
|
|
127
|
+
.scrollIndicators(.hidden)
|
|
158
128
|
}
|
|
129
|
+
}
|
|
159
130
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Text("10-Day Forecast")
|
|
163
|
-
.font(.headline)
|
|
164
|
-
|
|
165
|
-
ForEach(Array(forecast), id: \.date) { day in
|
|
166
|
-
HStack {
|
|
167
|
-
Text(day.date, format: .dateTime.weekday(.abbreviated))
|
|
168
|
-
.frame(width: 40, alignment: .leading)
|
|
169
|
-
|
|
170
|
-
Image(systemName: day.symbolName)
|
|
171
|
-
.symbolRenderingMode(.multicolor)
|
|
172
|
-
.frame(width: 30)
|
|
173
|
-
|
|
174
|
-
Text(day.lowTemperature.formatted())
|
|
175
|
-
.foregroundStyle(.secondary)
|
|
176
|
-
.frame(width: 50, alignment: .trailing)
|
|
177
|
-
|
|
178
|
-
temperatureBar(low: day.lowTemperature, high: day.highTemperature)
|
|
131
|
+
struct DayList: View {
|
|
132
|
+
let days: Forecast<DayWeather>
|
|
179
133
|
|
|
180
|
-
|
|
181
|
-
|
|
134
|
+
var body: some View {
|
|
135
|
+
Grid(alignment: .leading, verticalSpacing: 10) {
|
|
136
|
+
ForEach(days.forecast, id: \.date) { entry in
|
|
137
|
+
GridRow {
|
|
138
|
+
Text(entry.date.formatted(.dateTime.weekday(.abbreviated)))
|
|
139
|
+
Image(systemName: entry.symbolName).symbolRenderingMode(.multicolor)
|
|
140
|
+
Text(entry.lowTemperature.formatted(.measurement(width: .narrow)))
|
|
141
|
+
Capsule()
|
|
142
|
+
.fill(LinearGradient(colors: [.teal, .orange], startPoint: .leading, endPoint: .trailing))
|
|
143
|
+
.frame(maxHeight: 5)
|
|
144
|
+
.containerRelativeFrame(.horizontal) { width, _ in width * 0.35 }
|
|
145
|
+
Text(entry.highTemperature.formatted(.measurement(width: .narrow)))
|
|
182
146
|
}
|
|
183
|
-
.font(.subheadline)
|
|
184
147
|
}
|
|
185
148
|
}
|
|
186
149
|
}
|
|
150
|
+
}
|
|
187
151
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
high: Measurement<UnitTemperature>
|
|
191
|
-
) -> some View {
|
|
192
|
-
Capsule()
|
|
193
|
-
.fill(.linearGradient(
|
|
194
|
-
colors: [.blue, .orange],
|
|
195
|
-
startPoint: .leading,
|
|
196
|
-
endPoint: .trailing
|
|
197
|
-
))
|
|
198
|
-
.frame(height: 4)
|
|
199
|
-
.containerRelativeFrame(.horizontal) { length, _ in
|
|
200
|
-
length * 0.3
|
|
201
|
-
}
|
|
202
|
-
}
|
|
152
|
+
struct AlertList: View {
|
|
153
|
+
let alerts: [WeatherAlert]
|
|
203
154
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
HStack {
|
|
211
|
-
Image(systemName: "exclamationmark.triangle.fill")
|
|
212
|
-
.foregroundStyle(alert.severity == .extreme ? .red : .orange)
|
|
213
|
-
VStack(alignment: .leading) {
|
|
214
|
-
Text(alert.summary)
|
|
215
|
-
.font(.subheadline)
|
|
216
|
-
Text(alert.region)
|
|
217
|
-
.font(.caption)
|
|
218
|
-
.foregroundStyle(.secondary)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
.padding()
|
|
222
|
-
.background(.yellow.opacity(0.1))
|
|
223
|
-
.clipShape(.rect(cornerRadius: 8))
|
|
155
|
+
var body: some View {
|
|
156
|
+
ForEach(alerts, id: \.detailsURL) { warning in
|
|
157
|
+
let isExtreme = warning.severity == .extreme
|
|
158
|
+
Link(destination: warning.detailsURL) {
|
|
159
|
+
Label(warning.summary, systemImage: "exclamationmark.octagon.fill")
|
|
160
|
+
.foregroundStyle(isExtreme ? Color.red : Color.orange)
|
|
224
161
|
}
|
|
225
162
|
}
|
|
226
163
|
}
|
|
227
164
|
}
|
|
228
165
|
```
|
|
229
166
|
|
|
230
|
-
|
|
167
|
+
## Attribution view
|
|
168
|
+
|
|
169
|
+
A compact footer: a 12-point mark that falls back to the service name while it
|
|
170
|
+
loads, and a link to the data sources.
|
|
231
171
|
|
|
232
172
|
```swift
|
|
233
|
-
struct
|
|
234
|
-
let
|
|
235
|
-
@Environment(\.colorScheme) private var
|
|
173
|
+
struct CreditFooter: View {
|
|
174
|
+
let credit: WeatherAttribution
|
|
175
|
+
@Environment(\.colorScheme) private var scheme
|
|
236
176
|
|
|
237
177
|
var body: some View {
|
|
238
|
-
|
|
239
|
-
AsyncImage(url:
|
|
240
|
-
image
|
|
241
|
-
.resizable()
|
|
242
|
-
.scaledToFit()
|
|
243
|
-
.frame(height: 12)
|
|
178
|
+
HStack(spacing: 8) {
|
|
179
|
+
AsyncImage(url: scheme == .dark ? credit.combinedMarkDarkURL : credit.combinedMarkLightURL) { image in
|
|
180
|
+
image.resizable().scaledToFit()
|
|
244
181
|
} placeholder: {
|
|
245
|
-
Text(
|
|
246
|
-
.font(.caption2)
|
|
182
|
+
Text(credit.serviceName)
|
|
247
183
|
}
|
|
184
|
+
.frame(height: 12)
|
|
248
185
|
|
|
249
|
-
Link(destination:
|
|
250
|
-
Text("Data Sources")
|
|
251
|
-
.font(.caption2)
|
|
252
|
-
.foregroundStyle(.secondary)
|
|
253
|
-
}
|
|
186
|
+
Link("Data Sources", destination: credit.legalPageURL)
|
|
254
187
|
}
|
|
255
|
-
.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
private var markURL: URL {
|
|
259
|
-
colorScheme == .dark
|
|
260
|
-
? attribution.combinedMarkDarkURL
|
|
261
|
-
: attribution.combinedMarkLightURL
|
|
188
|
+
.font(.caption2)
|
|
189
|
+
.foregroundStyle(.secondary)
|
|
262
190
|
}
|
|
263
191
|
}
|
|
264
192
|
```
|
|
265
193
|
|
|
266
|
-
## Charts
|
|
194
|
+
## Charts integration
|
|
267
195
|
|
|
268
|
-
### Hourly
|
|
196
|
+
### Hourly temperature line
|
|
269
197
|
|
|
270
198
|
```swift
|
|
271
|
-
import SwiftUI
|
|
272
199
|
import Charts
|
|
273
|
-
import WeatherKit
|
|
274
200
|
|
|
275
|
-
struct
|
|
276
|
-
let
|
|
201
|
+
struct TemperatureTrend: View {
|
|
202
|
+
let hours: Forecast<HourWeather>
|
|
277
203
|
|
|
278
204
|
var body: some View {
|
|
279
|
-
Chart(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
AreaMark(
|
|
288
|
-
x: .value("Hour", hour.date),
|
|
289
|
-
y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
|
|
290
|
-
)
|
|
291
|
-
.interpolationMethod(.catmullRom)
|
|
292
|
-
.foregroundStyle(.orange.opacity(0.1))
|
|
205
|
+
Chart(hours.prefix(24), id: \.date) { slot in
|
|
206
|
+
let degrees = slot.temperature.converted(to: .celsius).value
|
|
207
|
+
AreaMark(x: .value("Time", slot.date), y: .value("Temperature", degrees))
|
|
208
|
+
.foregroundStyle(.orange.opacity(0.2))
|
|
209
|
+
.interpolationMethod(.catmullRom)
|
|
210
|
+
LineMark(x: .value("Time", slot.date), y: .value("Temperature", degrees))
|
|
211
|
+
.foregroundStyle(.orange)
|
|
212
|
+
.interpolationMethod(.catmullRom)
|
|
293
213
|
}
|
|
294
|
-
.chartYAxisLabel("Temperature (C)")
|
|
295
214
|
.chartXAxis {
|
|
296
215
|
AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
|
|
297
216
|
AxisGridLine()
|
|
298
217
|
AxisValueLabel(format: .dateTime.hour())
|
|
299
218
|
}
|
|
300
219
|
}
|
|
301
|
-
.frame(
|
|
220
|
+
.frame(minHeight: 160)
|
|
302
221
|
}
|
|
303
222
|
}
|
|
304
223
|
```
|
|
305
224
|
|
|
306
|
-
|
|
225
|
+
Convert to one unit before plotting; mixing units on one axis gives a
|
|
226
|
+
meaningless line. For display text, keep `formatted()` so the user's unit
|
|
227
|
+
applies.
|
|
228
|
+
|
|
229
|
+
### Daily precipitation bars
|
|
307
230
|
|
|
308
231
|
```swift
|
|
309
|
-
struct
|
|
310
|
-
let
|
|
232
|
+
struct RainOutlook: View {
|
|
233
|
+
let days: Forecast<DayWeather>
|
|
311
234
|
|
|
312
235
|
var body: some View {
|
|
313
|
-
Chart(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
.foregroundStyle(.blue.gradient)
|
|
236
|
+
Chart(days.forecast, id: \.date) { entry in
|
|
237
|
+
let odds = entry.precipitationChance
|
|
238
|
+
BarMark(x: .value("Date", entry.date, unit: .day), y: .value("Rain", odds))
|
|
239
|
+
.foregroundStyle(Color.blue.gradient)
|
|
319
240
|
}
|
|
320
241
|
.chartYScale(domain: 0...1)
|
|
321
242
|
.chartYAxis {
|
|
322
|
-
AxisMarks
|
|
243
|
+
AxisMarks { mark in
|
|
244
|
+
AxisValueLabel {
|
|
245
|
+
if let share = mark.as(Double.self) { Text(share, format: .percent) }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
323
248
|
}
|
|
324
249
|
.chartXAxis {
|
|
325
250
|
AxisMarks(values: .stride(by: .day)) { _ in
|
|
326
|
-
|
|
327
|
-
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
|
|
251
|
+
AxisValueLabel(format: .dateTime.weekday(.narrow), centered: true)
|
|
328
252
|
}
|
|
329
253
|
}
|
|
330
|
-
.frame(height: 150)
|
|
331
254
|
}
|
|
332
255
|
}
|
|
333
256
|
```
|
|
334
257
|
|
|
335
|
-
## Historical
|
|
258
|
+
## Historical weather statistics
|
|
336
259
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
260
|
+
iOS 18 added statistics that describe what is normal for a place, derived from
|
|
261
|
+
long-term records. The methods take variadic queries and return one result per
|
|
262
|
+
query.
|
|
340
263
|
|
|
341
264
|
```swift
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
let
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
265
|
+
@available(iOS 18.0, *)
|
|
266
|
+
func typicalJune(of year: Int, at place: CLLocation) async throws -> [(day: Int, low: String, high: String)] {
|
|
267
|
+
let calendar = Calendar(identifier: .gregorian)
|
|
268
|
+
let juneFirst = DateComponents(year: year, month: 6, day: 1)
|
|
269
|
+
let julyFirst = DateComponents(year: year, month: 7, day: 1)
|
|
270
|
+
guard let start = calendar.date(from: juneFirst), let end = calendar.date(from: julyFirst) else { return [] }
|
|
271
|
+
|
|
272
|
+
let service = WeatherService.shared
|
|
273
|
+
let (temperatures, precipitation) = try await service.dailyStatistics(
|
|
274
|
+
for: place,
|
|
275
|
+
forDaysIn: DateInterval(start: start, end: end),
|
|
276
|
+
including: .temperature, .precipitation
|
|
350
277
|
)
|
|
278
|
+
_ = precipitation.days.map(\.averagePrecipitationProbability)
|
|
351
279
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if let temp = dayStat.statistics(for: .temperature) {
|
|
355
|
-
print(" Avg temp: \(temp.mean?.formatted() ?? "N/A")")
|
|
356
|
-
print(" Min temp: \(temp.minimum?.formatted() ?? "N/A")")
|
|
357
|
-
print(" Max temp: \(temp.maximum?.formatted() ?? "N/A")")
|
|
358
|
-
}
|
|
280
|
+
return temperatures.days.map { stats in
|
|
281
|
+
(stats.day, stats.averageLowTemperature.formatted(), stats.averageHighTemperature.formatted())
|
|
359
282
|
}
|
|
360
283
|
}
|
|
361
284
|
```
|
|
362
285
|
|
|
363
|
-
|
|
286
|
+
- `DailyWeatherStatistics<T>` is a collection; its `days` array holds
|
|
287
|
+
`DayTemperatureStatistics` (`day`, `averageLowTemperature`,
|
|
288
|
+
`averageHighTemperature`) or `DayPrecipitationStatistics` (`day`,
|
|
289
|
+
`averagePrecipitationProbability`, `averagePrecipitationAmount`,
|
|
290
|
+
`averageSnowfallAmount`). `day` is a day number, not a `Date`.
|
|
291
|
+
- `monthlyStatistics(for:including:)` returns `MonthlyWeatherStatistics<T>`
|
|
292
|
+
whose `months` array holds `MonthTemperatureStatistics` (`month`,
|
|
293
|
+
`averageLowTemperature`, `averageHighTemperature`) or the precipitation
|
|
294
|
+
equivalent.
|
|
295
|
+
- `hourlyStatistics(for:forHoursIn:including:)` and
|
|
296
|
+
`dailySummary(for:forDaysIn:including:)` follow the same shape for hourly
|
|
297
|
+
normals and for observed daily totals.
|
|
364
298
|
|
|
365
299
|
```swift
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
for monthStat in stats {
|
|
373
|
-
print("Month: \(monthStat.month)")
|
|
374
|
-
}
|
|
300
|
+
@available(iOS 18.0, *)
|
|
301
|
+
func monthlyNormals(at place: CLLocation) async throws -> [Int: String] {
|
|
302
|
+
let service = WeatherService.shared
|
|
303
|
+
let normals = try await service.monthlyStatistics(for: place, including: .temperature)
|
|
304
|
+
return Dictionary(uniqueKeysWithValues: normals.months.map { ($0.month, $0.averageHighTemperature.formatted()) })
|
|
375
305
|
}
|
|
376
306
|
```
|
|
377
307
|
|
|
378
|
-
## Weather
|
|
308
|
+
## Weather condition mapping
|
|
379
309
|
|
|
380
|
-
|
|
310
|
+
`WeatherCondition` has a readable `description`, but apps often want their own
|
|
311
|
+
icon or grouping. Switch over every case and keep `@unknown default` for cases
|
|
312
|
+
added later.
|
|
381
313
|
|
|
382
314
|
```swift
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return .indigo
|
|
405
|
-
@unknown default:
|
|
406
|
-
return .primary
|
|
407
|
-
}
|
|
315
|
+
func skyGroup(_ condition: WeatherCondition) -> String {
|
|
316
|
+
switch condition {
|
|
317
|
+
case .clear, .mostlyClear, .hot:
|
|
318
|
+
return "sunny"
|
|
319
|
+
case .partlyCloudy, .mostlyCloudy, .cloudy:
|
|
320
|
+
return "cloudy"
|
|
321
|
+
case .drizzle, .rain, .heavyRain, .sunShowers:
|
|
322
|
+
return "rain"
|
|
323
|
+
case .snow, .heavySnow, .flurries, .sunFlurries, .blowingSnow, .blizzard:
|
|
324
|
+
return "snow"
|
|
325
|
+
case .sleet, .freezingRain, .freezingDrizzle, .wintryMix, .hail, .frigid:
|
|
326
|
+
return "ice"
|
|
327
|
+
case .thunderstorms, .isolatedThunderstorms, .scatteredThunderstorms,
|
|
328
|
+
.strongStorms, .tropicalStorm, .hurricane:
|
|
329
|
+
return "storm"
|
|
330
|
+
case .foggy, .haze, .smoky, .blowingDust:
|
|
331
|
+
return "low visibility"
|
|
332
|
+
case .breezy, .windy:
|
|
333
|
+
return "wind"
|
|
334
|
+
@unknown default:
|
|
335
|
+
return "unknown"
|
|
408
336
|
}
|
|
409
337
|
}
|
|
410
338
|
```
|
|
411
339
|
|
|
412
|
-
|
|
340
|
+
`WeatherSeverity` orders alerts. Rank it explicitly:
|
|
413
341
|
|
|
414
342
|
```swift
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return 2
|
|
424
|
-
case .minor:
|
|
425
|
-
return 1
|
|
426
|
-
case .unknown:
|
|
427
|
-
return 0
|
|
428
|
-
@unknown default:
|
|
429
|
-
return 0
|
|
430
|
-
}
|
|
343
|
+
func rank(_ severity: WeatherSeverity) -> Int {
|
|
344
|
+
switch severity {
|
|
345
|
+
case .extreme: 4
|
|
346
|
+
case .severe: 3
|
|
347
|
+
case .moderate: 2
|
|
348
|
+
case .minor: 1
|
|
349
|
+
case .unknown: 0
|
|
350
|
+
@unknown default: 0
|
|
431
351
|
}
|
|
432
352
|
}
|
|
433
353
|
```
|
|
434
354
|
|
|
435
|
-
## Caching
|
|
355
|
+
## Caching strategy
|
|
436
356
|
|
|
437
|
-
|
|
357
|
+
An actor keeps recent results per rounded coordinate and throws them away once
|
|
358
|
+
they are older than the staleness window.
|
|
438
359
|
|
|
439
360
|
```swift
|
|
440
|
-
actor
|
|
441
|
-
struct
|
|
442
|
-
let
|
|
443
|
-
let
|
|
444
|
-
let
|
|
445
|
-
let
|
|
361
|
+
actor ForecastCache {
|
|
362
|
+
struct Snapshot {
|
|
363
|
+
let now: CurrentWeather
|
|
364
|
+
let hours: Forecast<HourWeather>
|
|
365
|
+
let days: Forecast<DayWeather>
|
|
366
|
+
let fetchedAt: Date
|
|
446
367
|
}
|
|
447
368
|
|
|
448
|
-
private var
|
|
449
|
-
private let
|
|
369
|
+
private var store: [String: Snapshot] = [:]
|
|
370
|
+
private let maxAge: TimeInterval
|
|
371
|
+
|
|
372
|
+
init(maxAge: TimeInterval = 600) { self.maxAge = maxAge }
|
|
450
373
|
|
|
451
|
-
|
|
452
|
-
|
|
374
|
+
// Two decimal places is roughly a one-kilometre grid.
|
|
375
|
+
private func key(for place: CLLocation) -> String {
|
|
376
|
+
let spot = place.coordinate
|
|
377
|
+
return String(format: "%.2f|%.2f", spot.latitude, spot.longitude)
|
|
453
378
|
}
|
|
454
379
|
|
|
455
|
-
func
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
380
|
+
func snapshot(for place: CLLocation) -> Snapshot? {
|
|
381
|
+
let slot = key(for: place)
|
|
382
|
+
guard let hit = store[slot] else { return nil }
|
|
383
|
+
if Date.now.timeIntervalSince(hit.fetchedAt) > maxAge {
|
|
384
|
+
store[slot] = nil
|
|
459
385
|
return nil
|
|
460
386
|
}
|
|
461
|
-
return
|
|
387
|
+
return hit
|
|
462
388
|
}
|
|
463
389
|
|
|
464
|
-
func
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/// Generate a cache key from a location (rounded to ~1km precision)
|
|
469
|
-
static func key(for location: CLLocation) -> String {
|
|
470
|
-
let lat = (location.coordinate.latitude * 100).rounded() / 100
|
|
471
|
-
let lon = (location.coordinate.longitude * 100).rounded() / 100
|
|
472
|
-
return "\(lat),\(lon)"
|
|
390
|
+
func keep(_ snapshot: Snapshot, for place: CLLocation) {
|
|
391
|
+
store[key(for: place)] = snapshot
|
|
473
392
|
}
|
|
474
393
|
}
|
|
475
|
-
```
|
|
476
394
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
private let cache = WeatherCache()
|
|
485
|
-
|
|
486
|
-
var current: CurrentWeather?
|
|
487
|
-
|
|
488
|
-
func fetchWeather(for location: CLLocation) async throws {
|
|
489
|
-
let key = WeatherCache.key(for: location)
|
|
490
|
-
|
|
491
|
-
if let cached = await cache.get(for: key) {
|
|
492
|
-
current = cached.weather
|
|
493
|
-
return
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
let (current, hourly, daily) = try await service.weather(
|
|
497
|
-
for: location,
|
|
498
|
-
including: .current, .hourly, .daily
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
let entry = WeatherCache.CacheEntry(
|
|
502
|
-
weather: current,
|
|
503
|
-
hourly: hourly,
|
|
504
|
-
daily: daily,
|
|
505
|
-
fetchDate: .now
|
|
506
|
-
)
|
|
507
|
-
await cache.set(entry, for: key)
|
|
508
|
-
self.current = current
|
|
509
|
-
}
|
|
395
|
+
func freshForecast(at place: CLLocation, cache: ForecastCache) async throws -> ForecastCache.Snapshot {
|
|
396
|
+
if let cached = await cache.snapshot(for: place) { return cached }
|
|
397
|
+
let service = WeatherService.shared
|
|
398
|
+
let (now, hours, days) = try await service.weather(for: place, including: .current, .hourly, .daily)
|
|
399
|
+
let snapshot = ForecastCache.Snapshot(now: now, hours: hours, days: days, fetchedAt: .now)
|
|
400
|
+
await cache.keep(snapshot, for: place)
|
|
401
|
+
return snapshot
|
|
510
402
|
}
|
|
511
403
|
```
|
|
512
404
|
|
|
513
|
-
## Location-
|
|
405
|
+
## Location-based weather
|
|
514
406
|
|
|
515
|
-
|
|
407
|
+
A small location provider: ask for when-in-use permission, request one fix at
|
|
408
|
+
kilometre accuracy (plenty for weather), then fetch current conditions.
|
|
516
409
|
|
|
517
410
|
```swift
|
|
518
|
-
import CoreLocation
|
|
519
|
-
import WeatherKit
|
|
520
|
-
|
|
521
|
-
@Observable
|
|
522
411
|
@MainActor
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
private
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
var locationError: Error?
|
|
412
|
+
@Observable
|
|
413
|
+
final class LocalConditions: NSObject, CLLocationManagerDelegate {
|
|
414
|
+
private(set) var now: CurrentWeather?
|
|
415
|
+
private(set) var problem: String?
|
|
416
|
+
private let locator = CLLocationManager()
|
|
529
417
|
|
|
530
418
|
override init() {
|
|
531
419
|
super.init()
|
|
532
|
-
|
|
533
|
-
|
|
420
|
+
locator.delegate = self
|
|
421
|
+
locator.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
534
422
|
}
|
|
535
423
|
|
|
536
|
-
func
|
|
537
|
-
|
|
538
|
-
|
|
424
|
+
func start() {
|
|
425
|
+
locator.requestWhenInUseAuthorization()
|
|
426
|
+
locator.requestLocation()
|
|
539
427
|
}
|
|
540
428
|
|
|
541
|
-
nonisolated func locationManager(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
) {
|
|
545
|
-
guard let location = locations.last else { return }
|
|
546
|
-
Task { @MainActor in
|
|
547
|
-
do {
|
|
548
|
-
current = try await weatherService.weather(
|
|
549
|
-
for: location,
|
|
550
|
-
including: .current
|
|
551
|
-
)
|
|
552
|
-
} catch {
|
|
553
|
-
locationError = error
|
|
554
|
-
}
|
|
555
|
-
}
|
|
429
|
+
nonisolated func locationManager(_ source: CLLocationManager, didUpdateLocations fixes: [CLLocation]) {
|
|
430
|
+
guard let latest = fixes.last else { return }
|
|
431
|
+
Task { @MainActor in await refresh(at: latest) }
|
|
556
432
|
}
|
|
557
433
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
434
|
+
private func refresh(at spot: CLLocation) async {
|
|
435
|
+
do {
|
|
436
|
+
let service = WeatherService.shared
|
|
437
|
+
now = try await service.weather(for: spot, including: .current)
|
|
438
|
+
} catch {
|
|
439
|
+
problem = error.localizedDescription
|
|
564
440
|
}
|
|
565
441
|
}
|
|
442
|
+
|
|
443
|
+
nonisolated func locationManager(_ source: CLLocationManager, didFailWithError error: any Error) {
|
|
444
|
+
let message = error.localizedDescription
|
|
445
|
+
Task { @MainActor in problem = message }
|
|
446
|
+
}
|
|
566
447
|
}
|
|
567
448
|
```
|