@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
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 +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- 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 +345 -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 +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -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 +335 -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 +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -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 +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- 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 +228 -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 +302 -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 +236 -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/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -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/skill-creator/template.md +7 -1
- 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 +302 -241
- 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 +304 -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 +183 -162
- 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 +411 -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 +375 -491
- 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 +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -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 +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- 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 +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,500 +1,468 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pdfkit
|
|
3
|
-
description: "
|
|
3
|
+
description: "PDFKit on iOS: PDFView display and SwiftUI wrapping, creating and editing PDFDocument and PDFPage, highlight, note, signature and form annotations, PDFSelection search and text extraction, page navigation, thumbnails, form filling, encrypted saving. Use when an app displays, searches, annotates, fills, builds or saves PDFs."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
|
-
# PDFKit
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
# PDFKit
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
PDFKit renders, searches and edits PDF files. Most work touches five types:
|
|
11
|
+
`PDFView` (the on-screen viewer), `PDFDocument` (the file), `PDFPage` (one
|
|
12
|
+
page), `PDFAnnotation` (marks and form fields on a page) and `PDFSelection`
|
|
13
|
+
(a run of text). Examples target Swift 6.3 and iOS 26.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
- [Displaying PDFs](#displaying-pdfs)
|
|
15
|
-
- [Loading Documents](#loading-documents)
|
|
16
|
-
- [Page Navigation](#page-navigation)
|
|
17
|
-
- [Text Search and Selection](#text-search-and-selection)
|
|
18
|
-
- [Annotations](#annotations)
|
|
19
|
-
- [Thumbnails](#thumbnails)
|
|
20
|
-
- [SwiftUI Integration](#swiftui-integration)
|
|
21
|
-
- [Common Mistakes](#common-mistakes)
|
|
22
|
-
- [Review Checklist](#review-checklist)
|
|
23
|
-
- [References](#references)
|
|
15
|
+
Freehand drawing tools are not PDFKit work: use PencilKit or PaperKit.
|
|
24
16
|
|
|
25
17
|
## Setup
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
No entitlement and no Info.plist key is needed.
|
|
28
20
|
|
|
29
21
|
```swift
|
|
30
22
|
import PDFKit
|
|
31
23
|
```
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
| Platform | Minimum |
|
|
26
|
+
|---|---|
|
|
27
|
+
| iOS, iPadOS | 11 |
|
|
28
|
+
| Mac Catalyst | 13.1 |
|
|
29
|
+
| macOS | 10.4 |
|
|
30
|
+
| tvOS | 11 |
|
|
31
|
+
| visionOS | 1.0 |
|
|
34
32
|
|
|
35
|
-
##
|
|
33
|
+
## Showing a PDF
|
|
36
34
|
|
|
37
|
-
`PDFView` is a `UIView` subclass
|
|
38
|
-
|
|
35
|
+
`PDFView` is a `UIView` subclass. Out of the box it zooms, scrolls, selects text
|
|
36
|
+
and pages through the document.
|
|
39
37
|
|
|
40
38
|
```swift
|
|
41
39
|
import PDFKit
|
|
42
40
|
import UIKit
|
|
43
41
|
|
|
44
|
-
class
|
|
45
|
-
let
|
|
42
|
+
final class ManualViewController: UIViewController {
|
|
43
|
+
private let reader = PDFView()
|
|
46
44
|
|
|
47
45
|
override func viewDidLoad() {
|
|
48
46
|
super.viewDidLoad()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
reader.frame = view.bounds
|
|
48
|
+
reader.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
49
|
+
reader.autoScales = true
|
|
50
|
+
reader.displayMode = .singlePageContinuous
|
|
51
|
+
reader.displayDirection = .vertical
|
|
52
|
+
view.addSubview(reader)
|
|
53
|
+
|
|
54
|
+
if let url = Bundle.main.url(forResource: "assembly-guide", withExtension: "pdf"),
|
|
55
|
+
let file = PDFDocument(url: url) {
|
|
56
|
+
reader.document = file
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
### Display
|
|
62
|
+
### Display modes
|
|
65
63
|
|
|
66
|
-
| Mode |
|
|
64
|
+
| Mode | Layout |
|
|
67
65
|
|---|---|
|
|
68
|
-
| `.singlePage` |
|
|
69
|
-
| `.singlePageContinuous` |
|
|
70
|
-
| `.twoUp` |
|
|
71
|
-
| `.twoUpContinuous` |
|
|
66
|
+
| `.singlePage` | one page on screen at a time |
|
|
67
|
+
| `.singlePageContinuous` | one column of pages, scrolling |
|
|
68
|
+
| `.twoUp` | facing pages side by side |
|
|
69
|
+
| `.twoUpContinuous` | facing pages, scrolling |
|
|
72
70
|
|
|
73
|
-
###
|
|
71
|
+
### Scale and look
|
|
74
72
|
|
|
75
73
|
```swift
|
|
76
|
-
|
|
77
|
-
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
|
|
78
|
-
pdfView.maxScaleFactor = 4.0
|
|
74
|
+
import PDFKit
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
@MainActor
|
|
77
|
+
func tune(_ reader: PDFView) {
|
|
78
|
+
reader.autoScales = true
|
|
79
|
+
reader.minScaleFactor = reader.scaleFactorForSizeToFit
|
|
80
|
+
reader.maxScaleFactor = 5.0
|
|
81
|
+
reader.displaysPageBreaks = true
|
|
82
|
+
reader.pageShadowsEnabled = false
|
|
83
|
+
reader.interpolationQuality = .high
|
|
84
|
+
}
|
|
83
85
|
```
|
|
84
86
|
|
|
85
|
-
## Loading
|
|
87
|
+
## Loading documents
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
There are three initializers: `PDFDocument(url:)`, `PDFDocument(data:)` and
|
|
90
|
+
the empty `PDFDocument()`. The first two are failable.
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
let fileDoc = PDFDocument(url: fileURL)
|
|
91
|
-
let dataDoc = PDFDocument(data: pdfData)
|
|
92
|
-
let emptyDoc = PDFDocument()
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Password-Protected PDFs
|
|
92
|
+
### Locked files
|
|
96
93
|
|
|
97
94
|
```swift
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
import PDFKit
|
|
96
|
+
|
|
97
|
+
func open(_ file: PDFDocument, passcode: String) -> Bool {
|
|
98
|
+
guard file.isLocked else { return true }
|
|
99
|
+
return file.unlock(withPassword: passcode)
|
|
103
100
|
}
|
|
104
101
|
```
|
|
105
102
|
|
|
106
|
-
|
|
103
|
+
If `unlock(withPassword:)` returns `false`, ask the user for the password again.
|
|
104
|
+
|
|
105
|
+
### Saving and rearranging pages
|
|
107
106
|
|
|
108
107
|
```swift
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let count = document.pageCount
|
|
117
|
-
document.insert(PDFPage(), at: count)
|
|
118
|
-
if document.pageCount > 2 {
|
|
119
|
-
document.removePage(at: 2)
|
|
108
|
+
import PDFKit
|
|
109
|
+
|
|
110
|
+
func export(_ file: PDFDocument, to url: URL, ownerKey: String, readerKey: String) -> Bool {
|
|
111
|
+
file.write(to: url, withOptions: [
|
|
112
|
+
.ownerPasswordOption: ownerKey,
|
|
113
|
+
.userPasswordOption: readerKey
|
|
114
|
+
])
|
|
120
115
|
}
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
|
|
117
|
+
func reorder(_ file: PDFDocument) {
|
|
118
|
+
let blank = PDFPage()
|
|
119
|
+
file.insert(blank, at: file.pageCount)
|
|
120
|
+
if file.pageCount > 2 {
|
|
121
|
+
file.exchangePage(at: 0, withPageAt: 2)
|
|
122
|
+
}
|
|
123
|
+
if file.pageCount > 1 {
|
|
124
|
+
file.removePage(at: 1)
|
|
125
|
+
}
|
|
123
126
|
}
|
|
124
127
|
```
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if let document = pdfView.document,
|
|
134
|
-
pageIndex >= 0,
|
|
135
|
-
pageIndex < document.pageCount,
|
|
136
|
-
let page = document.page(at: pageIndex) {
|
|
137
|
-
pdfView.go(to: page)
|
|
138
|
-
}
|
|
129
|
+
- `write(to:)` saves as is; `write(to:withOptions:)` with
|
|
130
|
+
`.ownerPasswordOption` and `.userPasswordOption` encrypts.
|
|
131
|
+
- `dataRepresentation()` returns `Data?` for upload or sharing.
|
|
132
|
+
- Page indices start at 0. Inserting at `pageCount` appends.
|
|
133
|
+
- An index outside `0..<pageCount` raises an Objective-C exception rather than
|
|
134
|
+
returning `nil`, so check it before `page(at:)`, `insert(_:at:)`,
|
|
135
|
+
`removePage(at:)` or `exchangePage(at:withPageAt:)`.
|
|
139
136
|
|
|
140
|
-
|
|
141
|
-
pdfView.goToNextPage(nil)
|
|
142
|
-
pdfView.goToPreviousPage(nil)
|
|
143
|
-
pdfView.goToFirstPage(nil)
|
|
144
|
-
pdfView.goToLastPage(nil)
|
|
137
|
+
## Navigating
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
if pdfView.canGoToNextPage { /* ... */ }
|
|
139
|
+
`PDFView` keeps its own history of where the user has been.
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
```swift
|
|
142
|
+
import PDFKit
|
|
151
143
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
let
|
|
155
|
-
|
|
144
|
+
@MainActor
|
|
145
|
+
func show(pageNumber index: Int, in reader: PDFView) {
|
|
146
|
+
guard let file = reader.document, index >= 0, index < file.pageCount,
|
|
147
|
+
let target = file.page(at: index) else { return }
|
|
148
|
+
reader.go(to: target)
|
|
156
149
|
}
|
|
157
|
-
```
|
|
158
150
|
|
|
159
|
-
|
|
151
|
+
@MainActor
|
|
152
|
+
func stepThrough(_ reader: PDFView) {
|
|
153
|
+
if reader.canGoToNextPage { reader.goToNextPage(nil) }
|
|
154
|
+
reader.goToPreviousPage(nil)
|
|
155
|
+
reader.goToFirstPage(nil)
|
|
156
|
+
reader.goToLastPage(nil)
|
|
157
|
+
if reader.canGoBack { reader.goBack(nil) }
|
|
158
|
+
}
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
@objc func pageChanged(_ notification: Notification) {
|
|
168
|
-
guard let page = pdfView.currentPage,
|
|
169
|
-
let doc = pdfView.document else { return }
|
|
170
|
-
let index = doc.index(for: page)
|
|
171
|
-
pageLabel.text = "Page \(index + 1) of \(doc.pageCount)"
|
|
160
|
+
@MainActor
|
|
161
|
+
func jump(to point: CGPoint, on page: PDFPage, in reader: PDFView) {
|
|
162
|
+
reader.go(to: PDFDestination(page: page, at: point))
|
|
163
|
+
_ = reader.currentPage
|
|
172
164
|
}
|
|
173
165
|
```
|
|
174
166
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
### Synchronous Search
|
|
167
|
+
### Tracking the current page
|
|
178
168
|
|
|
179
169
|
```swift
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
170
|
+
import PDFKit
|
|
171
|
+
|
|
172
|
+
@MainActor
|
|
173
|
+
func watchPages(of reader: PDFView, update: @escaping @MainActor @Sendable (String) -> Void) -> NSObjectProtocol {
|
|
174
|
+
NotificationCenter.default.addObserver(forName: .PDFViewPageChanged, object: reader, queue: .main) { _ in
|
|
175
|
+
MainActor.assumeIsolated {
|
|
176
|
+
guard let file = reader.document, let page = reader.currentPage else { return }
|
|
177
|
+
update("\(file.index(for: page) + 1) / \(file.pageCount)")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
183
181
|
```
|
|
184
182
|
|
|
185
|
-
|
|
183
|
+
## Search and text
|
|
186
184
|
|
|
187
|
-
|
|
188
|
-
Implement `didMatchString(_:)` to receive each match and
|
|
189
|
-
`documentDidEndDocumentFind(_:)` for completion.
|
|
185
|
+
### Synchronous
|
|
190
186
|
|
|
191
|
-
|
|
187
|
+
`findString(_:withOptions:)` returns every match as `[PDFSelection]`:
|
|
192
188
|
|
|
193
189
|
```swift
|
|
194
|
-
|
|
195
|
-
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])
|
|
190
|
+
import PDFKit
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
func matches(of term: String, in file: PDFDocument) -> [PDFSelection] {
|
|
193
|
+
file.findString(term, withOptions: [.caseInsensitive])
|
|
194
|
+
}
|
|
199
195
|
```
|
|
200
196
|
|
|
201
|
-
###
|
|
197
|
+
### Asynchronous
|
|
198
|
+
|
|
199
|
+
For long documents, search in the background with `beginFindString(_:withOptions:)`
|
|
200
|
+
and a `PDFDocumentDelegate`. `didMatchString(_:)` is called per hit and
|
|
201
|
+
`documentDidEndDocumentFind(_:)` when the pass finishes.
|
|
202
202
|
|
|
203
203
|
```swift
|
|
204
|
-
|
|
205
|
-
let firstPage = document.pageCount > 0 ? document.page(at: 0) : nil
|
|
206
|
-
let pageText = firstPage?.string // Single page
|
|
207
|
-
let attributed = firstPage?.attributedString // With formatting
|
|
208
|
-
|
|
209
|
-
// Region-based extraction
|
|
210
|
-
if let page = firstPage {
|
|
211
|
-
let selection = page.selection(for: CGRect(x: 50, y: 50, width: 400, height: 200))
|
|
212
|
-
let text = selection?.string
|
|
213
|
-
}
|
|
214
|
-
```
|
|
204
|
+
import PDFKit
|
|
215
205
|
|
|
216
|
-
|
|
206
|
+
final class ContractSearch: NSObject, PDFDocumentDelegate {
|
|
207
|
+
private(set) var hits: [PDFSelection] = []
|
|
208
|
+
var onFinish: (([PDFSelection]) -> Void)?
|
|
217
209
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
210
|
+
func run(_ term: String, in file: PDFDocument) {
|
|
211
|
+
hits.removeAll()
|
|
212
|
+
file.delegate = self
|
|
213
|
+
file.beginFindString(term, withOptions: [.caseInsensitive])
|
|
214
|
+
}
|
|
222
215
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
216
|
+
func didMatchString(_ instance: PDFSelection) {
|
|
217
|
+
hits.append(instance)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
func documentDidEndDocumentFind(_ notification: Notification) {
|
|
221
|
+
onFinish?(hits)
|
|
222
|
+
}
|
|
226
223
|
}
|
|
227
224
|
```
|
|
228
225
|
|
|
229
|
-
|
|
226
|
+
### Next match and the system find bar
|
|
230
227
|
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
- `findString(_:fromSelection:withOptions:)` returns the first hit after a
|
|
229
|
+
given selection, which is how "Find next" works.
|
|
230
|
+
- `reader.isFindInteractionEnabled = true` turns on the system find bar
|
|
231
|
+
(iOS 16+).
|
|
233
232
|
|
|
234
|
-
###
|
|
233
|
+
### Extracting text
|
|
235
234
|
|
|
236
235
|
```swift
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
236
|
+
import PDFKit
|
|
237
|
+
|
|
238
|
+
func textSamples(from file: PDFDocument) -> (all: String?, first: String?, box: String?) {
|
|
239
|
+
let all = file.string
|
|
240
|
+
guard file.pageCount > 0, let first = file.page(at: 0) else { return (all, nil, nil) }
|
|
241
|
+
_ = first.attributedString
|
|
242
|
+
let box = first.selection(for: CGRect(x: 36, y: 700, width: 300, height: 60))?.string
|
|
243
|
+
return (all, first.string, box)
|
|
244
244
|
}
|
|
245
245
|
```
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
bounds: CGRect(x: 100, y: 700, width: 30, height: 30),
|
|
252
|
-
forType: .text, withProperties: nil
|
|
253
|
-
)
|
|
254
|
-
note.contents = "This is a sticky note."
|
|
255
|
-
note.color = .systemYellow
|
|
256
|
-
note.iconType = .comment
|
|
257
|
-
page.addAnnotation(note)
|
|
258
|
-
```
|
|
247
|
+
`document.string` is the whole document, `page.string` one page,
|
|
248
|
+
`page.attributedString` keeps fonts and styles, and `page.selection(for:)`
|
|
249
|
+
limits extraction to a rectangle. Check `pageCount > 0` before asking for
|
|
250
|
+
page 0.
|
|
259
251
|
|
|
260
|
-
###
|
|
252
|
+
### Highlighting hits
|
|
261
253
|
|
|
262
254
|
```swift
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
import PDFKit
|
|
256
|
+
|
|
257
|
+
@MainActor
|
|
258
|
+
func mark(_ hits: [PDFSelection], in reader: PDFView) {
|
|
259
|
+
for hit in hits { hit.color = .systemOrange }
|
|
260
|
+
reader.highlightedSelections = hits
|
|
261
|
+
if let firstHit = hits.first {
|
|
262
|
+
reader.setCurrentSelection(firstHit, animate: true)
|
|
263
|
+
reader.go(to: firstHit)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
271
266
|
```
|
|
272
267
|
|
|
273
|
-
|
|
268
|
+
## Annotations
|
|
269
|
+
|
|
270
|
+
Create an annotation with `PDFAnnotation(bounds:forType:withProperties:)` and
|
|
271
|
+
attach it with `page.addAnnotation(_:)`.
|
|
274
272
|
|
|
275
273
|
```swift
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
forType: .link, withProperties: nil
|
|
279
|
-
)
|
|
280
|
-
link.url = URL(string: "https://example.com")
|
|
281
|
-
page.addAnnotation(link)
|
|
282
|
-
|
|
283
|
-
// Internal page link
|
|
284
|
-
link.destination = PDFDestination(page: targetPage, at: .zero)
|
|
285
|
-
```
|
|
274
|
+
import PDFKit
|
|
275
|
+
import UIKit
|
|
286
276
|
|
|
287
|
-
|
|
277
|
+
func annotate(_ page: PDFPage, hit: PDFSelection, target: PDFPage) {
|
|
278
|
+
let marker = PDFAnnotation(bounds: hit.bounds(for: page), forType: .highlight, withProperties: nil)
|
|
279
|
+
marker.color = UIColor.systemGreen.withAlphaComponent(0.5)
|
|
280
|
+
page.addAnnotation(marker)
|
|
281
|
+
|
|
282
|
+
let note = PDFAnnotation(bounds: CGRect(x: 40, y: 40, width: 24, height: 24), forType: .text, withProperties: nil)
|
|
283
|
+
note.contents = "Check this total"
|
|
284
|
+
note.color = .systemBlue
|
|
285
|
+
note.iconType = .comment
|
|
286
|
+
page.addAnnotation(note)
|
|
287
|
+
|
|
288
|
+
let label = PDFAnnotation(bounds: CGRect(x: 72, y: 120, width: 220, height: 30), forType: .freeText, withProperties: nil)
|
|
289
|
+
label.contents = "Approved"
|
|
290
|
+
label.font = .boldSystemFont(ofSize: 16)
|
|
291
|
+
label.fontColor = .systemRed
|
|
292
|
+
page.addAnnotation(label)
|
|
293
|
+
|
|
294
|
+
let web = PDFAnnotation(bounds: CGRect(x: 72, y: 170, width: 160, height: 20), forType: .link, withProperties: nil)
|
|
295
|
+
web.url = URL(string: "https://example.org/terms")
|
|
296
|
+
page.addAnnotation(web)
|
|
297
|
+
|
|
298
|
+
let internalJump = PDFAnnotation(bounds: CGRect(x: 72, y: 200, width: 160, height: 20), forType: .link, withProperties: nil)
|
|
299
|
+
internalJump.destination = PDFDestination(page: target, at: CGPoint(x: 0, y: 792))
|
|
300
|
+
page.addAnnotation(internalJump)
|
|
301
|
+
}
|
|
288
302
|
|
|
289
|
-
|
|
290
|
-
for
|
|
291
|
-
|
|
303
|
+
func stripMarkup(from page: PDFPage) {
|
|
304
|
+
for item in page.annotations {
|
|
305
|
+
page.removeAnnotation(item)
|
|
306
|
+
}
|
|
292
307
|
}
|
|
293
308
|
```
|
|
294
309
|
|
|
295
|
-
|
|
296
|
-
`.freeText`, `.ink`, `.link`, `.line`, `.square`, `.circle`, `.stamp`,
|
|
310
|
+
Commonly used subtypes: `.highlight`, `.underline`, `.strikeOut`, `.text`,
|
|
311
|
+
`.freeText`, `.ink`, `.link`, `.line`, `.square`, `.circle`, `.stamp`,
|
|
297
312
|
`.widget`.
|
|
298
313
|
|
|
299
314
|
## Thumbnails
|
|
300
315
|
|
|
301
316
|
### PDFThumbnailView
|
|
302
317
|
|
|
303
|
-
`PDFThumbnailView` shows a strip of page
|
|
318
|
+
`PDFThumbnailView` shows a strip of page previews. Point its `pdfView` at the
|
|
319
|
+
main viewer so taps navigate and the current page is highlighted.
|
|
304
320
|
|
|
305
321
|
```swift
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
import PDFKit
|
|
323
|
+
|
|
324
|
+
@MainActor
|
|
325
|
+
func makeStrip(for reader: PDFView) -> PDFThumbnailView {
|
|
326
|
+
let strip = PDFThumbnailView()
|
|
327
|
+
strip.pdfView = reader
|
|
328
|
+
strip.thumbnailSize = CGSize(width: 48, height: 64)
|
|
329
|
+
strip.layoutMode = .vertical
|
|
330
|
+
return strip
|
|
331
|
+
}
|
|
312
332
|
```
|
|
313
333
|
|
|
314
|
-
### Generating
|
|
334
|
+
### Generating images
|
|
315
335
|
|
|
316
336
|
```swift
|
|
317
|
-
|
|
337
|
+
import PDFKit
|
|
338
|
+
import UIKit
|
|
318
339
|
|
|
319
|
-
|
|
320
|
-
let
|
|
321
|
-
|
|
340
|
+
func previews(of file: PDFDocument) -> [UIImage] {
|
|
341
|
+
let size = CGSize(width: 120, height: 160)
|
|
342
|
+
let cover = file.page(at: 0)?.thumbnail(of: size, for: .mediaBox)
|
|
343
|
+
_ = cover
|
|
344
|
+
return (0..<file.pageCount).compactMap { file.page(at: $0)?.thumbnail(of: size, for: .mediaBox) }
|
|
322
345
|
}
|
|
323
346
|
```
|
|
324
347
|
|
|
325
|
-
## SwiftUI
|
|
348
|
+
## SwiftUI
|
|
326
349
|
|
|
327
|
-
Wrap `PDFView` in a `UIViewRepresentable
|
|
328
|
-
|
|
329
|
-
belong
|
|
350
|
+
Wrap `PDFView` in a `UIViewRepresentable`. Wrappers that configure the viewer,
|
|
351
|
+
move between pages, manage annotations, search, show thumbnails or install
|
|
352
|
+
overlays belong to this skill. Only generic representable questions (lifecycle,
|
|
353
|
+
sizing, state architecture) go to `swiftui-uikit-interop`.
|
|
330
354
|
|
|
331
355
|
```swift
|
|
332
|
-
import SwiftUI
|
|
333
356
|
import PDFKit
|
|
357
|
+
import SwiftUI
|
|
334
358
|
|
|
335
|
-
struct
|
|
359
|
+
struct PDFReader: UIViewRepresentable {
|
|
336
360
|
let document: PDFDocument
|
|
337
361
|
|
|
338
362
|
func makeUIView(context: Context) -> PDFView {
|
|
339
|
-
let
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return
|
|
363
|
+
let reader = PDFView()
|
|
364
|
+
reader.autoScales = true
|
|
365
|
+
reader.displayMode = .singlePageContinuous
|
|
366
|
+
reader.document = document
|
|
367
|
+
return reader
|
|
344
368
|
}
|
|
345
369
|
|
|
346
|
-
func updateUIView(_
|
|
347
|
-
if
|
|
348
|
-
|
|
370
|
+
func updateUIView(_ reader: PDFView, context: Context) {
|
|
371
|
+
if reader.document !== document {
|
|
372
|
+
reader.document = document
|
|
349
373
|
}
|
|
350
374
|
}
|
|
351
375
|
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### Usage
|
|
355
376
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
let url: URL
|
|
377
|
+
struct InvoiceScreen: View {
|
|
378
|
+
let fileURL: URL
|
|
359
379
|
|
|
360
380
|
var body: some View {
|
|
361
|
-
if let
|
|
362
|
-
|
|
381
|
+
if let file = PDFDocument(url: fileURL) {
|
|
382
|
+
PDFReader(document: file)
|
|
363
383
|
.ignoresSafeArea()
|
|
364
384
|
} else {
|
|
365
|
-
ContentUnavailableView("
|
|
385
|
+
ContentUnavailableView("Cannot open invoice", systemImage: "doc.questionmark")
|
|
366
386
|
}
|
|
367
387
|
}
|
|
368
388
|
}
|
|
369
389
|
```
|
|
370
390
|
|
|
371
|
-
|
|
372
|
-
coordinator
|
|
391
|
+
A version that binds the current page, reports annotation taps and uses a
|
|
392
|
+
coordinator is in [the patterns reference](references/pdfkit-patterns.md#coordinator-based-swiftui-wrapper).
|
|
373
393
|
|
|
374
|
-
### Page
|
|
394
|
+
### Page overlays (iOS 16+)
|
|
375
395
|
|
|
376
|
-
`PDFPageOverlayViewProvider` places UIKit
|
|
377
|
-
|
|
396
|
+
`PDFPageOverlayViewProvider` places a UIKit view above each page, for
|
|
397
|
+
interactive controls or custom drawing. Implement
|
|
398
|
+
`pdfView(_:overlayViewFor:) -> UIView?` and assign the provider to
|
|
399
|
+
`pdfView.pageOverlayViewProvider`. That property is weak, so something else
|
|
400
|
+
must keep the provider alive.
|
|
378
401
|
|
|
379
402
|
```swift
|
|
380
|
-
|
|
403
|
+
import PDFKit
|
|
404
|
+
import UIKit
|
|
405
|
+
|
|
406
|
+
@MainActor
|
|
407
|
+
final class StampOverlays: NSObject, @preconcurrency PDFPageOverlayViewProvider {
|
|
381
408
|
func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
409
|
+
let badge = UILabel()
|
|
410
|
+
badge.text = "DRAFT"
|
|
411
|
+
badge.textColor = .systemRed
|
|
412
|
+
badge.sizeToFit()
|
|
413
|
+
return badge
|
|
385
414
|
}
|
|
386
415
|
}
|
|
387
416
|
|
|
388
|
-
class
|
|
389
|
-
let
|
|
390
|
-
private let
|
|
417
|
+
final class DraftViewer: UIViewController {
|
|
418
|
+
private let reader = PDFView()
|
|
419
|
+
private let overlays = StampOverlays()
|
|
391
420
|
|
|
392
421
|
override func viewDidLoad() {
|
|
393
422
|
super.viewDidLoad()
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
`pageOverlayViewProvider` is weak, so keep the provider strongly owned. For overlay lifecycle and save handling, read [references/pdfkit-patterns.md](references/pdfkit-patterns.md).
|
|
400
|
-
|
|
401
|
-
## Common Mistakes
|
|
402
|
-
|
|
403
|
-
### DON'T: Force-unwrap PDFDocument init
|
|
404
|
-
|
|
405
|
-
`PDFDocument(url:)` and `PDFDocument(data:)` are failable initializers.
|
|
406
|
-
|
|
407
|
-
```swift
|
|
408
|
-
// WRONG
|
|
409
|
-
let document = PDFDocument(url: url)!
|
|
410
|
-
|
|
411
|
-
// CORRECT
|
|
412
|
-
guard let document = PDFDocument(url: url) else { return }
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### DON'T: Forget autoScales on PDFView
|
|
416
|
-
|
|
417
|
-
Without `autoScales`, the PDF renders at its native resolution.
|
|
418
|
-
|
|
419
|
-
```swift
|
|
420
|
-
// WRONG
|
|
421
|
-
pdfView.document = document
|
|
422
|
-
|
|
423
|
-
// CORRECT
|
|
424
|
-
pdfView.autoScales = true
|
|
425
|
-
pdfView.document = document
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### DON'T: Ignore PDF coordinate system in annotations
|
|
429
|
-
|
|
430
|
-
PDF page coordinates have origin at the bottom-left with Y increasing
|
|
431
|
-
upward -- opposite of UIKit.
|
|
432
|
-
|
|
433
|
-
```swift
|
|
434
|
-
// WRONG: UIKit coordinates
|
|
435
|
-
let bounds = CGRect(x: 50, y: 50, width: 200, height: 30)
|
|
436
|
-
|
|
437
|
-
// CORRECT: PDF coordinates (origin bottom-left)
|
|
438
|
-
let pageBounds = page.bounds(for: .mediaBox)
|
|
439
|
-
let pdfY = pageBounds.height - 50 - 30
|
|
440
|
-
let bounds = CGRect(x: 50, y: pdfY, width: 200, height: 30)
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### DON'T: Modify annotations on a background thread
|
|
444
|
-
|
|
445
|
-
PDFKit classes are not thread-safe.
|
|
446
|
-
|
|
447
|
-
```swift
|
|
448
|
-
// WRONG
|
|
449
|
-
DispatchQueue.global().async { page.addAnnotation(annotation) }
|
|
450
|
-
|
|
451
|
-
// CORRECT
|
|
452
|
-
DispatchQueue.main.async { page.addAnnotation(annotation) }
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### DON'T: Compare PDFDocument with == in UIViewRepresentable
|
|
456
|
-
|
|
457
|
-
`PDFDocument` is a reference type. Use identity (`!==`).
|
|
458
|
-
|
|
459
|
-
```swift
|
|
460
|
-
// WRONG: Always replaces document
|
|
461
|
-
func updateUIView(_ pdfView: PDFView, context: Context) {
|
|
462
|
-
pdfView.document = document
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// CORRECT
|
|
466
|
-
func updateUIView(_ pdfView: PDFView, context: Context) {
|
|
467
|
-
if pdfView.document !== document {
|
|
468
|
-
pdfView.document = document
|
|
423
|
+
reader.pageOverlayViewProvider = overlays
|
|
469
424
|
}
|
|
470
425
|
}
|
|
471
426
|
```
|
|
472
427
|
|
|
473
|
-
##
|
|
474
|
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
-
|
|
478
|
-
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
428
|
+
## Common mistakes
|
|
429
|
+
|
|
430
|
+
- **Force-unwrapping the initializers.** `PDFDocument(url:)` and
|
|
431
|
+
`PDFDocument(data:)` return `nil` for a bad file. Use `guard let`.
|
|
432
|
+
- **Forgetting `autoScales`.** Without it, pages appear at their native size.
|
|
433
|
+
- **Using UIKit coordinates on a page.** PDF space starts at the bottom-left and
|
|
434
|
+
y grows upward. To place something at a UIKit-style top offset, compute
|
|
435
|
+
`page.bounds(for: .mediaBox).height - topOffset - itemHeight`.
|
|
436
|
+
- **Mutating from a background queue.** PDFKit classes are not thread-safe.
|
|
437
|
+
Add or change annotations on the main thread.
|
|
438
|
+
- **Reassigning the document in `updateUIView`.** `PDFDocument` is a class;
|
|
439
|
+
compare with `!==` first, otherwise every SwiftUI update reloads the file.
|
|
440
|
+
|
|
441
|
+
## Review checklist
|
|
442
|
+
|
|
443
|
+
- [ ] `PDFDocument` is created with optional binding
|
|
444
|
+
- [ ] `autoScales = true` is set
|
|
445
|
+
- [ ] Every page index is checked against `pageCount`
|
|
446
|
+
- [ ] `displayMode` and `displayDirection` match the design
|
|
447
|
+
- [ ] Annotation rectangles are in PDF coordinate space
|
|
448
|
+
- [ ] All PDFKit mutation happens on the main thread
|
|
449
|
+
- [ ] Locked files go through `isLocked` and `unlock(withPassword:)`
|
|
450
|
+
- [ ] The SwiftUI wrapper compares documents with `!==` in `updateUIView`
|
|
451
|
+
- [ ] `PDFViewPageChanged` drives any page indicator
|
|
452
|
+
- [ ] `PDFThumbnailView.pdfView` points at the main `PDFView`
|
|
453
|
+
- [ ] Long documents are searched with `beginFindString` and the delegate
|
|
454
|
+
- [ ] `write(to:withOptions:)` is used when the file must be encrypted
|
|
487
455
|
|
|
488
456
|
## References
|
|
489
457
|
|
|
490
|
-
-
|
|
491
|
-
- [PDFKit
|
|
492
|
-
- [PDFView](https://
|
|
493
|
-
- [PDFDocument](https://
|
|
494
|
-
- [PDFPage](https://
|
|
495
|
-
- [PDFAnnotation](https://
|
|
496
|
-
- [PDFSelection](https://
|
|
497
|
-
- [PDFThumbnailView](https://
|
|
498
|
-
- [PDFPageOverlayViewProvider](https://
|
|
499
|
-
- [Adding
|
|
500
|
-
- [Adding
|
|
458
|
+
- [PDFKit patterns](references/pdfkit-patterns.md) - forms, building PDFs, watermarks, merging and splitting, printing, outlines, custom annotation and page classes, overlay lifecycle, rendering, rotation and crop, burn-in, permissions, coordinator wrapper
|
|
459
|
+
- [PDFKit](https://developer.apple.com/documentation/pdfkit)
|
|
460
|
+
- [PDFView](https://developer.apple.com/documentation/pdfkit/pdfview)
|
|
461
|
+
- [PDFDocument](https://developer.apple.com/documentation/pdfkit/pdfdocument)
|
|
462
|
+
- [PDFPage](https://developer.apple.com/documentation/pdfkit/pdfpage)
|
|
463
|
+
- [PDFAnnotation](https://developer.apple.com/documentation/pdfkit/pdfannotation)
|
|
464
|
+
- [PDFSelection](https://developer.apple.com/documentation/pdfkit/pdfselection)
|
|
465
|
+
- [PDFThumbnailView](https://developer.apple.com/documentation/pdfkit/pdfthumbnailview)
|
|
466
|
+
- [PDFPageOverlayViewProvider](https://developer.apple.com/documentation/pdfkit/pdfpageoverlayviewprovider)
|
|
467
|
+
- [Adding widgets to a PDF document](https://developer.apple.com/documentation/pdfkit/adding-widgets-to-a-pdf-document)
|
|
468
|
+
- [Adding custom graphics to a PDF](https://developer.apple.com/documentation/pdfkit/adding-custom-graphics-to-a-pdf)
|