@devo-bmad-custom/agent-orchestration 1.0.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/bin/bmad.js +36 -0
- package/lib/cli.js +137 -0
- package/lib/filter.js +73 -0
- package/lib/installer.js +787 -0
- package/package.json +30 -0
- package/src/.agents/skills/audit-website/README.md +20 -0
- package/src/.agents/skills/audit-website/SKILL.md +470 -0
- package/src/.agents/skills/audit-website/agents/openai.yaml +6 -0
- package/src/.agents/skills/audit-website/assets/icon-small.svg +41 -0
- package/src/.agents/skills/audit-website/references/OUTPUT-FORMAT.md +250 -0
- package/src/.agents/skills/clean-code-standards/SKILL.md +105 -0
- package/src/.agents/skills/excalidraw-dark-standard/SKILL.md +282 -0
- package/src/.agents/skills/excalidraw-diagram-generator/SKILL.md +613 -0
- package/src/.agents/skills/excalidraw-diagram-generator/references/element-types.md +497 -0
- package/src/.agents/skills/excalidraw-diagram-generator/references/excalidraw-schema.md +350 -0
- package/src/.agents/skills/excalidraw-diagram-generator/scripts/README.md +193 -0
- package/src/.agents/skills/excalidraw-diagram-generator/scripts/add-arrow.py +312 -0
- package/src/.agents/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py +404 -0
- package/src/.agents/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py +183 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw +334 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw +558 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw +279 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw +662 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw +179 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw +244 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw +145 -0
- package/src/.agents/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw +509 -0
- package/src/.agents/skills/frontend-responsive-design-standards/SKILL.md +434 -0
- package/src/.agents/skills/java-fundamentals/SKILL.md +116 -0
- package/src/.agents/skills/java-performance/SKILL.md +119 -0
- package/src/.agents/skills/next-best-practices/SKILL.md +153 -0
- package/src/.agents/skills/next-best-practices/async-patterns.md +87 -0
- package/src/.agents/skills/next-best-practices/bundling.md +180 -0
- package/src/.agents/skills/next-best-practices/data-patterns.md +297 -0
- package/src/.agents/skills/next-best-practices/debug-tricks.md +105 -0
- package/src/.agents/skills/next-best-practices/directives.md +73 -0
- package/src/.agents/skills/next-best-practices/error-handling.md +227 -0
- package/src/.agents/skills/next-best-practices/file-conventions.md +140 -0
- package/src/.agents/skills/next-best-practices/font.md +245 -0
- package/src/.agents/skills/next-best-practices/functions.md +108 -0
- package/src/.agents/skills/next-best-practices/hydration-error.md +91 -0
- package/src/.agents/skills/next-best-practices/image.md +173 -0
- package/src/.agents/skills/next-best-practices/metadata.md +301 -0
- package/src/.agents/skills/next-best-practices/parallel-routes.md +287 -0
- package/src/.agents/skills/next-best-practices/route-handlers.md +146 -0
- package/src/.agents/skills/next-best-practices/rsc-boundaries.md +159 -0
- package/src/.agents/skills/next-best-practices/runtime-selection.md +39 -0
- package/src/.agents/skills/next-best-practices/scripts.md +141 -0
- package/src/.agents/skills/next-best-practices/self-hosting.md +371 -0
- package/src/.agents/skills/next-best-practices/suspense-boundaries.md +67 -0
- package/src/.agents/skills/nextjs-app-router-patterns/SKILL.md +537 -0
- package/src/.agents/skills/postgresql-optimization/SKILL.md +404 -0
- package/src/.agents/skills/python-backend/SKILL.md +153 -0
- package/src/.agents/skills/python-fundamentals/SKILL.md +234 -0
- package/src/.agents/skills/python-performance/SKILL.md +404 -0
- package/src/.agents/skills/react-expert/SKILL.md +335 -0
- package/src/.agents/skills/redis-best-practices/SKILL.md +438 -0
- package/src/.agents/skills/security-best-practices/SKILL.md +288 -0
- package/src/.agents/skills/security-review/LICENSE +22 -0
- package/src/.agents/skills/security-review/SKILL.md +312 -0
- package/src/.agents/skills/security-review/infrastructure/docker.md +432 -0
- package/src/.agents/skills/security-review/languages/javascript.md +388 -0
- package/src/.agents/skills/security-review/languages/python.md +363 -0
- package/src/.agents/skills/security-review/references/api-security.md +519 -0
- package/src/.agents/skills/security-review/references/authentication.md +353 -0
- package/src/.agents/skills/security-review/references/authorization.md +372 -0
- package/src/.agents/skills/security-review/references/business-logic.md +443 -0
- package/src/.agents/skills/security-review/references/cryptography.md +329 -0
- package/src/.agents/skills/security-review/references/csrf.md +398 -0
- package/src/.agents/skills/security-review/references/data-protection.md +378 -0
- package/src/.agents/skills/security-review/references/deserialization.md +410 -0
- package/src/.agents/skills/security-review/references/error-handling.md +436 -0
- package/src/.agents/skills/security-review/references/file-security.md +457 -0
- package/src/.agents/skills/security-review/references/injection.md +259 -0
- package/src/.agents/skills/security-review/references/logging.md +433 -0
- package/src/.agents/skills/security-review/references/misconfiguration.md +435 -0
- package/src/.agents/skills/security-review/references/modern-threats.md +475 -0
- package/src/.agents/skills/security-review/references/ssrf.md +415 -0
- package/src/.agents/skills/security-review/references/supply-chain.md +405 -0
- package/src/.agents/skills/security-review/references/xss.md +336 -0
- package/src/.agents/skills/subagent-driven-development/SKILL.md +275 -0
- package/src/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/src/.agents/skills/subagent-driven-development/implementer-prompt.md +113 -0
- package/src/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/src/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/src/.agents/skills/systematic-debugging/SKILL.md +296 -0
- package/src/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/src/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/src/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/src/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
- package/src/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/src/.agents/skills/systematic-debugging/test-academic.md +14 -0
- package/src/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/src/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/src/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/src/.agents/skills/typescript-best-practices/SKILL.md +373 -0
- package/src/.agents/skills/ui-ux-pro-custom/SKILL.md +348 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/charts.csv +26 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/colors.csv +97 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/icons.csv +101 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/SKILL.md +106 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/accessibility.md +475 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/animation.md +466 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/composition-locals.md +231 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/deprecated-patterns.md +323 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/lists-scrolling.md +400 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/modifiers.md +331 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/navigation.md +416 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/performance.md +446 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/side-effects.md +516 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/foundation-source.md +13327 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/material3-source.md +19097 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/navigation-source.md +2947 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/runtime-source.md +11316 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/ui-source.md +7896 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/state-management.md +377 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/styles-experimental.md +470 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/theming-material3.md +349 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/view-composition.md +595 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/landing.csv +31 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/mobile-ui-layout.md +654 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/products.csv +97 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/react-performance.csv +45 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/astro.csv +54 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/flutter.csv +53 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/html-tailwind.csv +56 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/jetpack-compose.csv +53 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nextjs.csv +53 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxt-ui.csv +51 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxtjs.csv +59 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react-native.csv +56 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react.csv +54 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/shadcn.csv +61 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/svelte.csv +54 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/swiftui.csv +51 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/vue.csv +50 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/styles.csv +68 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/SKILL.md +438 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/references/alarmkit-patterns.md +584 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-clips/SKILL.md +436 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/SKILL.md +489 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/references/appintents-advanced.md +1076 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/SKILL.md +340 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/privacy-manifest.md +90 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/review-checklists.md +106 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/SKILL.md +500 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-conversion.md +425 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-optimization.md +344 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/foundation-models.md +508 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/mlx-swift.md +285 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/SKILL.md +496 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/references/keychain-biometric.md +211 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/SKILL.md +499 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/references/background-task-patterns.md +390 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/SKILL.md +461 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/references/callkit-patterns.md +425 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/SKILL.md +492 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/references/cloudkit-patterns.md +461 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/codable-patterns/SKILL.md +467 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/SKILL.md +425 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/references/contacts-patterns.md +409 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/SKILL.md +491 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/references/ble-patterns.md +435 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/SKILL.md +388 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/references/motion-patterns.md +405 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/SKILL.md +495 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/references/nfc-patterns.md +420 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/SKILL.md +459 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/references/coreml-swift-integration.md +765 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/SKILL.md +422 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/instruments-guide.md +387 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/lldb-patterns.md +298 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/device-integrity/SKILL.md +477 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/SKILL.md +460 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/references/energykit-patterns.md +541 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/SKILL.md +483 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/references/eventkit-patterns.md +326 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/SKILL.md +498 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/references/healthkit-patterns.md +602 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/SKILL.md +496 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/references/matter-commissioning.md +455 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/SKILL.md +301 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/references/a11y-patterns.md +140 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/SKILL.md +418 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/formatstyle-locale.md +627 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/string-catalogs.md +462 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/SKILL.md +441 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/background-websocket.md +862 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/lightweight-clients.md +93 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/network-framework.md +563 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/urlsession-patterns.md +1116 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/SKILL.md +496 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/app-review-guidelines.md +174 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/cryptokit-advanced.md +297 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/file-storage-patterns.md +354 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/privacy-manifest.md +117 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/SKILL.md +500 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/references/live-activity-patterns.md +868 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/SKILL.md +485 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/corelocation-patterns.md +730 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/mapkit-patterns.md +748 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/metrickit-diagnostics/SKILL.md +479 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/SKILL.md +395 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/references/musickit-patterns.md +363 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/SKILL.md +412 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/references/translation-patterns.md +311 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/SKILL.md +398 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/references/wallet-passes.md +254 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/SKILL.md +387 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/paperkit-integration.md +376 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/pencilkit-patterns.md +302 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/SKILL.md +446 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/references/permissionkit-patterns.md +435 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/SKILL.md +501 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/av-playback.md +701 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/camera-capture.md +774 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/image-loading-caching.md +869 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/photospicker-patterns.md +597 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/SKILL.md +501 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/notification-patterns.md +677 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/rich-notifications.md +745 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/SKILL.md +479 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/references/realitykit-patterns.md +480 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/SKILL.md +483 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/references/shareplay-patterns.md +544 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/speech-recognition/SKILL.md +485 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/SKILL.md +478 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/app-review-guidelines.md +58 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/storekit-advanced.md +755 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/SKILL.md +487 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/references/charts-patterns.md +895 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/SKILL.md +408 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/approachable-concurrency.md +80 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swift-6-2-concurrency.md +233 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swiftui-concurrency.md +187 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/synchronization-primitives.md +341 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/SKILL.md +498 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/references/swift-patterns-extended.md +505 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/SKILL.md +467 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/references/testing-patterns.md +504 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/SKILL.md +334 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/core-data-coexistence.md +504 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-advanced.md +975 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-queries.md +675 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/SKILL.md +481 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/animation-advanced.md +804 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/core-animation-bridge.md +553 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/SKILL.md +450 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/references/gesture-patterns.md +425 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/SKILL.md +336 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/form.md +97 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/grids.md +69 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/list.md +99 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/scrollview.md +147 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/SKILL.md +325 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/references/liquid-glass.md +387 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/SKILL.md +262 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/deeplinks.md +207 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/navigationstack.md +177 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/sheets.md +169 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/tabview.md +178 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/SKILL.md +381 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/architecture-patterns.md +486 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/deprecated-migration.md +1097 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/design-polish.md +780 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/platform-and-sharing.md +696 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/SKILL.md +491 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/SKILL.md +428 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/hosting-migration.md +534 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/representable-recipes.md +1133 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/SKILL.md +494 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/references/tipkit-patterns.md +782 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +58 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +100 -0
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -0
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -0
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -0
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -0
- package/src/.agents/skills/ux-audit/SKILL.md +151 -0
- package/src/.agents/skills/websocket-engineer/SKILL.md +168 -0
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -0
- package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -0
- package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -0
- package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -0
- package/src/.agents/skills/websocket-engineer/references/security.md +474 -0
- package/src/.agents/skills/writing-skills/SKILL.md +655 -0
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -0
- package/src/.agents/skills/writing-skills/render-graphs.js +168 -0
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/src/.claude/commands/bmad-track-compact.md +19 -0
- package/src/.claude/commands/bmad-track-extended.md +19 -0
- package/src/.claude/commands/bmad-track-large.md +19 -0
- package/src/.claude/commands/bmad-track-medium.md +19 -0
- package/src/.claude/commands/bmad-track-nano.md +19 -0
- package/src/.claude/commands/bmad-track-rv.md +18 -0
- package/src/.claude/commands/bmad-track-small.md +19 -0
- package/src/_memory/config.yaml +11 -0
- package/src/_memory/master-orchestrator-sidecar/docs-index.md +3 -0
- package/src/_memory/master-orchestrator-sidecar/instructions.md +2566 -0
- package/src/_memory/master-orchestrator-sidecar/memories.md +8 -0
- package/src/_memory/master-orchestrator-sidecar/session-state.md +15 -0
- package/src/_memory/master-orchestrator-sidecar/triage-history.md +3 -0
- package/src/_memory/master-orchestrator-sidecar/workflows-overview.html +1230 -0
- package/src/_memory/skills/excalidraw/SKILL.md +78 -0
- package/src/_memory/skills/excalidraw/diagram-patterns.md +53 -0
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -0
- package/src/_memory/skills/writing-skills/SKILL.md +655 -0
- package/src/bmb/agents/agent-builder.md +59 -0
- package/src/bmb/agents/module-builder.md +60 -0
- package/src/bmb/agents/workflow-builder.md +61 -0
- package/src/bmb/config.yaml +12 -0
- package/src/bmb/module-help.csv +13 -0
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -0
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -0
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -0
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -0
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -0
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -0
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -0
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -0
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -0
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -0
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -0
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +17 -0
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -0
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -0
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -0
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -0
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -0
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -0
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -0
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -0
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -0
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -0
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -0
- package/src/bmb/workflows/agent/steps-e/e-03-placeholder.md +1 -0
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -0
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -0
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -0
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -0
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -0
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -0
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -0
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -0
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -0
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -0
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -0
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -0
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -0
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -0
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -0
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -0
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -0
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -0
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -0
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -0
- package/src/bmb/workflows/module/data/module-standards.md +263 -0
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -0
- package/src/bmb/workflows/module/module-help-generate.md +254 -0
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -0
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -0
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -0
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -0
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -0
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -0
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -0
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -0
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -0
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -0
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -0
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -0
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -0
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -0
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -0
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -0
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -0
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -0
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -0
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -0
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -0
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -0
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -0
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -0
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -0
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -0
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -0
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -0
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -0
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -0
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -0
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -0
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -0
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -0
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -0
- package/src/bmb/workflows/module/templates/brief-template.md +154 -0
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -0
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -0
- package/src/bmb/workflows/module/workflow-create-module.md +86 -0
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -0
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -0
- package/src/bmb/workflows/workflow/data/architecture.md +150 -0
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -0
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -0
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -0
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -0
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -0
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -0
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -0
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -0
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -0
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -0
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -0
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -0
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -0
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -0
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -0
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -0
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -0
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -0
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -0
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -0
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -0
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -0
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -0
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -0
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -0
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -0
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -0
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -0
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -0
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -0
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -0
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -0
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -0
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -0
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -0
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -0
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -0
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -0
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -0
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -0
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -0
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -0
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -0
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -0
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -0
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -0
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -0
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -0
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -0
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -0
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -0
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -0
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -0
- package/src/bmm/agents/analyst.md +104 -0
- package/src/bmm/agents/architect.md +85 -0
- package/src/bmm/agents/dev.md +100 -0
- package/src/bmm/agents/pm.md +98 -0
- package/src/bmm/agents/qa.md +90 -0
- package/src/bmm/agents/quick-flow-solo-dev.md +92 -0
- package/src/bmm/agents/review-agent.md +129 -0
- package/src/bmm/agents/sm.md +90 -0
- package/src/bmm/agents/tech-writer/tech-writer.md +94 -0
- package/src/bmm/agents/ux-designer.md +124 -0
- package/src/bmm/data/project-context-template.md +26 -0
- package/src/bmm/module-help.csv +31 -0
- package/src/bmm/teams/default-party.csv +20 -0
- package/src/bmm/teams/team-fullstack.yaml +12 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/product-brief.template.md +10 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +115 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +107 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +141 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +144 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +147 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +161 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +99 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +87 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +156 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +165 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +140 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +152 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +345 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +92 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +164 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +174 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +184 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +105 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +360 -0
- package/src/bmm/workflows/1-analysis/research/research.template.md +29 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +87 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +165 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +174 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +141 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +159 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +387 -0
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +15 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md +197 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/project-types.csv +11 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01-init.md +139 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +100 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +160 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +88 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +99 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +169 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +156 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +136 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +176 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +184 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +174 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +175 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +189 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +162 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +79 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +183 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +149 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +187 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +192 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +108 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +166 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +131 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +150 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +118 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +155 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +170 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +158 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +147 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +182 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +202 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +148 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +201 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +179 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +164 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/templates/prd-template.md +10 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +65 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01-init.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +106 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +111 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +115 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +127 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +167 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +143 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +118 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +154 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +136 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +165 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +135 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +192 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +101 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +13 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +45 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +185 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +129 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +130 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +93 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +196 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/templates/readiness-report-template.md +4 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/architecture-decision-template.md +12 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +13 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/data/project-types.csv +7 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01-init.md +89 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +82 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +106 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +138 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +129 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +166 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +186 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +163 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +38 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +129 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +124 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +122 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +84 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -0
- package/src/bmm/workflows/4-implementation/code-review/checklist.md +23 -0
- package/src/bmm/workflows/4-implementation/code-review/instructions.xml +227 -0
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +43 -0
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -0
- package/src/bmm/workflows/4-implementation/correct-course/instructions.md +207 -0
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +53 -0
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +159 -0
- package/src/bmm/workflows/4-implementation/create-story/instructions.xml +574 -0
- package/src/bmm/workflows/4-implementation/create-story/template.md +79 -0
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +52 -0
- package/src/bmm/workflows/4-implementation/dev-story/checklist.md +80 -0
- package/src/bmm/workflows/4-implementation/dev-story/instructions.xml +493 -0
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +20 -0
- package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -0
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +52 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/checklist.md +33 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/instructions.md +232 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +52 -0
- package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -0
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +158 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +122 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +93 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +93 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +87 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +204 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +152 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +123 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +201 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -0
- package/src/bmm/workflows/document-project/checklist.md +245 -0
- package/src/bmm/workflows/document-project/documentation-requirements.csv +12 -0
- package/src/bmm/workflows/document-project/instructions.md +130 -0
- package/src/bmm/workflows/document-project/templates/deep-dive-template.md +345 -0
- package/src/bmm/workflows/document-project/templates/index-template.md +169 -0
- package/src/bmm/workflows/document-project/templates/project-overview-template.md +103 -0
- package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -0
- package/src/bmm/workflows/document-project/templates/source-tree-template.md +135 -0
- package/src/bmm/workflows/document-project/workflow.yaml +22 -0
- package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -0
- package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -0
- package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -0
- package/src/bmm/workflows/document-project/workflows/full-scan.yaml +31 -0
- package/src/bmm/workflows/generate-project-context/project-context-template.md +21 -0
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -0
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +322 -0
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +235 -0
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -0
- package/src/bmm/workflows/qa/automate/workflow.yaml +233 -0
- package/src/bmm/workflows/qa-generate-e2e-tests/checklist.md +33 -0
- package/src/bmm/workflows/qa-generate-e2e-tests/instructions.md +110 -0
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +42 -0
- package/src/core/agents/bmad-master.md +56 -0
- package/src/core/agents/master-orchestrator.md +54 -0
- package/src/core/config.yaml +9 -0
- package/src/core/module-help.csv +10 -0
- package/src/core/scripts/generate-loop-report.py +72 -0
- package/src/core/skills/prepare-to-merge/SKILL.md +77 -0
- package/src/core/tasks/editorial-review-prose.xml +102 -0
- package/src/core/tasks/editorial-review-structure.xml +208 -0
- package/src/core/tasks/help.md +86 -0
- package/src/core/tasks/index-docs.xml +65 -0
- package/src/core/tasks/review-adversarial-general.xml +66 -0
- package/src/core/tasks/review-adversarial-loop.xml +46 -0
- package/src/core/tasks/review-edge-case-hunter.xml +63 -0
- package/src/core/tasks/review-party-loop.xml +46 -0
- package/src/core/tasks/shard-doc.xml +108 -0
- package/src/core/tasks/workflow.xml +236 -0
- package/src/core/templates/review-loop-report.html +88 -0
- package/src/core/templates/review-loop-report.md +5 -0
- package/src/core/workflows/advanced-elicitation/methods.csv +51 -0
- package/src/core/workflows/advanced-elicitation/workflow.xml +118 -0
- package/src/core/workflows/brainstorming/brain-methods.csv +62 -0
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +212 -0
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -0
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -0
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -0
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -0
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -0
- package/src/core/workflows/brainstorming/steps/step-02e-deep-dive.md +68 -0
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +403 -0
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -0
- package/src/core/workflows/brainstorming/template.md +15 -0
- package/src/core/workflows/brainstorming/workflow.md +60 -0
- package/src/core/workflows/extract-trackers/workflow.md +45 -0
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +142 -0
- package/src/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -0
- package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -0
- package/src/core/workflows/party-mode/workflow.md +194 -0
- package/src/docs/dev/tmux/actions_popup.py +291 -0
- package/src/docs/dev/tmux/actions_popup.sh +110 -0
- package/src/docs/dev/tmux/claude_usage.sh +15 -0
- package/src/docs/dev/tmux/colors.conf +34 -0
- package/src/docs/dev/tmux/cpu_usage.sh +7 -0
- package/src/docs/dev/tmux/dispatch.sh +10 -0
- package/src/docs/dev/tmux/float_init.sh +13 -0
- package/src/docs/dev/tmux/float_term.sh +23 -0
- package/src/docs/dev/tmux/open_clip.sh +14 -0
- package/src/docs/dev/tmux/paste_clipboard.sh +13 -0
- package/src/docs/dev/tmux/paste_image_wrapper.sh +83 -0
- package/src/docs/dev/tmux/ram_usage.sh +3 -0
- package/src/docs/dev/tmux/title_sync.sh +54 -0
- package/src/docs/dev/tmux/tmux-setup.md +806 -0
- package/src/docs/dev/tmux/tmux.conf +127 -0
- package/src/docs/dev/tmux/xclip +18 -0
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
# Representable Recipes
|
|
2
|
+
|
|
3
|
+
Complete working recipes for common UIKit wrapping scenarios. Each recipe includes the full `UIViewRepresentable` or `UIViewControllerRepresentable` struct, the Coordinator with delegate methods, a SwiftUI usage example, and gotchas specific to that wrapper.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [1. WKWebView Wrapper](#1-wkwebview-wrapper)
|
|
10
|
+
- [2. MKMapView Wrapper](#2-mkmapview-wrapper)
|
|
11
|
+
- [3. UITextView Wrapper (Attributed Text)](#3-uitextview-wrapper-attributed-text)
|
|
12
|
+
- [4. AVCaptureVideoPreviewLayer Wrapper](#4-avcapturevideopreviewlayer-wrapper)
|
|
13
|
+
- [5. PHPickerViewController Wrapper](#5-phpickerviewcontroller-wrapper)
|
|
14
|
+
- [6. MFMailComposeViewController Wrapper](#6-mfmailcomposeviewcontroller-wrapper)
|
|
15
|
+
- [7. UIActivityViewController Wrapper (Share Sheet)](#7-uiactivityviewcontroller-wrapper-share-sheet)
|
|
16
|
+
- [8. UISearchBar Wrapper](#8-uisearchbar-wrapper)
|
|
17
|
+
- [9. SFSafariViewController Wrapper](#9-sfsafariviewcontroller-wrapper)
|
|
18
|
+
- [10. PDFView Wrapper (PDFKit)](#10-pdfview-wrapper-pdfkit)
|
|
19
|
+
- [11. MFMessageComposeViewController Wrapper](#11-mfmessagecomposeviewcontroller-wrapper)
|
|
20
|
+
|
|
21
|
+
## 1. WKWebView Wrapper
|
|
22
|
+
|
|
23
|
+
Load URLs, handle navigation, expose back/forward state, and bridge JavaScript.
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
import SwiftUI
|
|
27
|
+
import WebKit
|
|
28
|
+
|
|
29
|
+
struct WebView: UIViewRepresentable {
|
|
30
|
+
let url: URL
|
|
31
|
+
@Binding var isLoading: Bool
|
|
32
|
+
@Binding var canGoBack: Bool
|
|
33
|
+
@Binding var canGoForward: Bool
|
|
34
|
+
var onNavigationFinished: ((URL?) -> Void)?
|
|
35
|
+
|
|
36
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
37
|
+
|
|
38
|
+
func makeUIView(context: Context) -> WKWebView {
|
|
39
|
+
let config = WKWebViewConfiguration()
|
|
40
|
+
let webView = WKWebView(frame: .zero, configuration: config)
|
|
41
|
+
webView.navigationDelegate = context.coordinator
|
|
42
|
+
webView.allowsBackForwardNavigationGestures = true
|
|
43
|
+
|
|
44
|
+
// KVO for loading and navigation state
|
|
45
|
+
context.coordinator.observe(webView)
|
|
46
|
+
|
|
47
|
+
webView.load(URLRequest(url: url))
|
|
48
|
+
return webView
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func updateUIView(_ uiView: WKWebView, context: Context) {
|
|
52
|
+
// Only reload if the URL changed
|
|
53
|
+
if uiView.url != url {
|
|
54
|
+
uiView.load(URLRequest(url: url))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
|
|
59
|
+
coordinator.cancellables.removeAll()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func goBack(_ webView: WKWebView) { webView.goBack() }
|
|
63
|
+
func goForward(_ webView: WKWebView) { webView.goForward() }
|
|
64
|
+
|
|
65
|
+
final class Coordinator: NSObject, WKNavigationDelegate {
|
|
66
|
+
var parent: WebView
|
|
67
|
+
var cancellables: [NSKeyValueObservation] = []
|
|
68
|
+
|
|
69
|
+
init(_ parent: WebView) { self.parent = parent }
|
|
70
|
+
|
|
71
|
+
func observe(_ webView: WKWebView) {
|
|
72
|
+
cancellables.append(
|
|
73
|
+
webView.observe(\.isLoading) { [weak self] webView, _ in
|
|
74
|
+
self?.parent.isLoading = webView.isLoading
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
cancellables.append(
|
|
78
|
+
webView.observe(\.canGoBack) { [weak self] webView, _ in
|
|
79
|
+
self?.parent.canGoBack = webView.canGoBack
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
cancellables.append(
|
|
83
|
+
webView.observe(\.canGoForward) { [weak self] webView, _ in
|
|
84
|
+
self?.parent.canGoForward = webView.canGoForward
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
90
|
+
parent.onNavigationFinished?(webView.url)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func webView(
|
|
94
|
+
_ webView: WKWebView,
|
|
95
|
+
decidePolicyFor navigationAction: WKNavigationAction
|
|
96
|
+
) async -> WKNavigationActionPolicy {
|
|
97
|
+
.allow
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Usage
|
|
104
|
+
|
|
105
|
+
```swift
|
|
106
|
+
struct BrowserView: View {
|
|
107
|
+
@State private var isLoading = false
|
|
108
|
+
@State private var canGoBack = false
|
|
109
|
+
@State private var canGoForward = false
|
|
110
|
+
|
|
111
|
+
var body: some View {
|
|
112
|
+
WebView(
|
|
113
|
+
url: URL(string: "https://example.com")!,
|
|
114
|
+
isLoading: $isLoading,
|
|
115
|
+
canGoBack: $canGoBack,
|
|
116
|
+
canGoForward: $canGoForward
|
|
117
|
+
)
|
|
118
|
+
.overlay(alignment: .top) {
|
|
119
|
+
if isLoading { ProgressView() }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Gotchas
|
|
126
|
+
|
|
127
|
+
- **WKWebView must not be created in `updateUIView`.** It is expensive to allocate and resets all navigation state.
|
|
128
|
+
- **KVO observations must be cleaned up.** Store `NSKeyValueObservation` references and clear them in `dismantleUIView`.
|
|
129
|
+
- **JavaScript bridge:** If adding `WKScriptMessageHandler`, use the coordinator as the handler and register it on the configuration before creating the web view.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 2. MKMapView Wrapper
|
|
134
|
+
|
|
135
|
+
Display a map with annotations, track region changes, and toggle map type.
|
|
136
|
+
|
|
137
|
+
```swift
|
|
138
|
+
import SwiftUI
|
|
139
|
+
import MapKit
|
|
140
|
+
|
|
141
|
+
struct MapViewRepresentable: UIViewRepresentable {
|
|
142
|
+
@Binding var region: MKCoordinateRegion
|
|
143
|
+
@Binding var mapType: MKMapType
|
|
144
|
+
var annotations: [MKPointAnnotation]
|
|
145
|
+
var onRegionChanged: ((MKCoordinateRegion) -> Void)?
|
|
146
|
+
|
|
147
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
148
|
+
|
|
149
|
+
func makeUIView(context: Context) -> MKMapView {
|
|
150
|
+
let mapView = MKMapView()
|
|
151
|
+
mapView.delegate = context.coordinator
|
|
152
|
+
mapView.showsUserLocation = true
|
|
153
|
+
return mapView
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func updateUIView(_ uiView: MKMapView, context: Context) {
|
|
157
|
+
// Update map type
|
|
158
|
+
if uiView.mapType != mapType {
|
|
159
|
+
uiView.mapType = mapType
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Update region -- guard against tiny differences to avoid feedback loops
|
|
163
|
+
let currentCenter = uiView.region.center
|
|
164
|
+
let threshold = 0.0001
|
|
165
|
+
if abs(currentCenter.latitude - region.center.latitude) > threshold ||
|
|
166
|
+
abs(currentCenter.longitude - region.center.longitude) > threshold {
|
|
167
|
+
uiView.setRegion(region, animated: true)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Diff annotations
|
|
171
|
+
let existing = Set(uiView.annotations.compactMap { $0 as? MKPointAnnotation })
|
|
172
|
+
let incoming = Set(annotations)
|
|
173
|
+
let toRemove = existing.subtracting(incoming)
|
|
174
|
+
let toAdd = incoming.subtracting(existing)
|
|
175
|
+
uiView.removeAnnotations(Array(toRemove))
|
|
176
|
+
uiView.addAnnotations(Array(toAdd))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
final class Coordinator: NSObject, MKMapViewDelegate {
|
|
180
|
+
var parent: MapViewRepresentable
|
|
181
|
+
|
|
182
|
+
init(_ parent: MapViewRepresentable) { self.parent = parent }
|
|
183
|
+
|
|
184
|
+
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
|
|
185
|
+
parent.region = mapView.region
|
|
186
|
+
parent.onRegionChanged?(mapView.region)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func mapView(
|
|
190
|
+
_ mapView: MKMapView,
|
|
191
|
+
viewFor annotation: MKAnnotation
|
|
192
|
+
) -> MKAnnotationView? {
|
|
193
|
+
guard !(annotation is MKUserLocation) else { return nil }
|
|
194
|
+
let id = "pin"
|
|
195
|
+
let view = mapView.dequeueReusableAnnotationView(withIdentifier: id)
|
|
196
|
+
?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: id)
|
|
197
|
+
view.annotation = annotation
|
|
198
|
+
return view
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Usage
|
|
205
|
+
|
|
206
|
+
```swift
|
|
207
|
+
struct MapScreen: View {
|
|
208
|
+
@State private var region = MKCoordinateRegion(
|
|
209
|
+
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
|
|
210
|
+
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
|
|
211
|
+
)
|
|
212
|
+
@State private var mapType: MKMapType = .standard
|
|
213
|
+
|
|
214
|
+
var body: some View {
|
|
215
|
+
MapViewRepresentable(
|
|
216
|
+
region: $region,
|
|
217
|
+
mapType: $mapType,
|
|
218
|
+
annotations: []
|
|
219
|
+
)
|
|
220
|
+
.ignoresSafeArea()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Gotchas
|
|
226
|
+
|
|
227
|
+
- **Region update loops.** The delegate writes to `@Binding region`, which triggers `updateUIView`, which calls `setRegion`, which triggers the delegate again. The threshold guard is essential.
|
|
228
|
+
- **Annotation diffing.** MKMapView does not handle duplicate annotations well. Always diff before adding/removing.
|
|
229
|
+
- **Native SwiftUI Map.** For iOS 17+, prefer the native `Map` view unless you need delegate-level control (custom overlays, clustering, etc.).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 3. UITextView Wrapper (Attributed Text)
|
|
234
|
+
|
|
235
|
+
Wrap `UITextView` for rich text editing with `NSAttributedString` binding and placeholder support.
|
|
236
|
+
|
|
237
|
+
```swift
|
|
238
|
+
import SwiftUI
|
|
239
|
+
|
|
240
|
+
struct RichTextEditor: UIViewRepresentable {
|
|
241
|
+
@Binding var attributedText: NSAttributedString
|
|
242
|
+
var placeholder: String = ""
|
|
243
|
+
@Binding var isFirstResponder: Bool
|
|
244
|
+
|
|
245
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
246
|
+
|
|
247
|
+
func makeUIView(context: Context) -> UITextView {
|
|
248
|
+
let textView = UITextView()
|
|
249
|
+
textView.delegate = context.coordinator
|
|
250
|
+
textView.font = .preferredFont(forTextStyle: .body)
|
|
251
|
+
textView.adjustsFontForContentSizeCategory = true
|
|
252
|
+
textView.backgroundColor = .clear
|
|
253
|
+
textView.textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4)
|
|
254
|
+
|
|
255
|
+
// Placeholder label
|
|
256
|
+
let label = UILabel()
|
|
257
|
+
label.text = placeholder
|
|
258
|
+
label.font = .preferredFont(forTextStyle: .body)
|
|
259
|
+
label.textColor = .placeholderText
|
|
260
|
+
label.tag = 999
|
|
261
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
262
|
+
textView.addSubview(label)
|
|
263
|
+
NSLayoutConstraint.activate([
|
|
264
|
+
label.topAnchor.constraint(equalTo: textView.topAnchor, constant: 8),
|
|
265
|
+
label.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 8),
|
|
266
|
+
])
|
|
267
|
+
|
|
268
|
+
return textView
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
func updateUIView(_ uiView: UITextView, context: Context) {
|
|
272
|
+
if uiView.attributedText != attributedText {
|
|
273
|
+
uiView.attributedText = attributedText
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Update placeholder visibility
|
|
277
|
+
if let label = uiView.viewWithTag(999) as? UILabel {
|
|
278
|
+
label.isHidden = !uiView.text.isEmpty
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// First responder management
|
|
282
|
+
if isFirstResponder && !uiView.isFirstResponder {
|
|
283
|
+
uiView.becomeFirstResponder()
|
|
284
|
+
} else if !isFirstResponder && uiView.isFirstResponder {
|
|
285
|
+
uiView.resignFirstResponder()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
@available(iOS 16.0, *)
|
|
290
|
+
func sizeThatFits(
|
|
291
|
+
_ proposal: ProposedViewSize,
|
|
292
|
+
uiView: UITextView,
|
|
293
|
+
context: Context
|
|
294
|
+
) -> CGSize? {
|
|
295
|
+
let width = proposal.width ?? UIView.layoutFittingExpandedSize.width
|
|
296
|
+
let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
297
|
+
return CGSize(width: width, height: max(size.height, 44))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
final class Coordinator: NSObject, UITextViewDelegate {
|
|
301
|
+
var parent: RichTextEditor
|
|
302
|
+
|
|
303
|
+
init(_ parent: RichTextEditor) { self.parent = parent }
|
|
304
|
+
|
|
305
|
+
func textViewDidChange(_ textView: UITextView) {
|
|
306
|
+
parent.attributedText = textView.attributedText ?? NSAttributedString()
|
|
307
|
+
if let label = textView.viewWithTag(999) as? UILabel {
|
|
308
|
+
label.isHidden = !textView.text.isEmpty
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
func textViewDidBeginEditing(_ textView: UITextView) {
|
|
313
|
+
parent.isFirstResponder = true
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
func textViewDidEndEditing(_ textView: UITextView) {
|
|
317
|
+
parent.isFirstResponder = false
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Usage
|
|
324
|
+
|
|
325
|
+
```swift
|
|
326
|
+
struct NotesEditorView: View {
|
|
327
|
+
@State private var text = NSAttributedString()
|
|
328
|
+
@State private var isFocused = false
|
|
329
|
+
|
|
330
|
+
var body: some View {
|
|
331
|
+
RichTextEditor(
|
|
332
|
+
attributedText: $text,
|
|
333
|
+
placeholder: "Write something...",
|
|
334
|
+
isFirstResponder: $isFocused
|
|
335
|
+
)
|
|
336
|
+
.frame(minHeight: 100)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Gotchas
|
|
342
|
+
|
|
343
|
+
- **`NSAttributedString` comparison.** The equality check in `updateUIView` is critical -- without it, every keystroke triggers a full re-render loop.
|
|
344
|
+
- **First responder management.** Avoid calling `becomeFirstResponder()` unconditionally in `updateUIView` -- it steals focus from other fields.
|
|
345
|
+
- **iOS 26 alternative.** `TextEditor` in iOS 26 supports `AttributedString` natively. Prefer it unless you need `NSAttributedString` or delegate-level control.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 4. AVCaptureVideoPreviewLayer Wrapper
|
|
350
|
+
|
|
351
|
+
Display a live camera preview. The preview layer requires a `UIView` host.
|
|
352
|
+
|
|
353
|
+
```swift
|
|
354
|
+
import SwiftUI
|
|
355
|
+
import AVFoundation
|
|
356
|
+
|
|
357
|
+
struct CameraPreview: UIViewRepresentable {
|
|
358
|
+
let session: AVCaptureSession
|
|
359
|
+
|
|
360
|
+
func makeUIView(context: Context) -> CameraPreviewUIView {
|
|
361
|
+
let view = CameraPreviewUIView()
|
|
362
|
+
view.previewLayer.session = session
|
|
363
|
+
view.previewLayer.videoGravity = .resizeAspectFill
|
|
364
|
+
return view
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
func updateUIView(_ uiView: CameraPreviewUIView, context: Context) {
|
|
368
|
+
// Session is reference type -- no update needed unless swapping sessions
|
|
369
|
+
if uiView.previewLayer.session !== session {
|
|
370
|
+
uiView.previewLayer.session = session
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
final class CameraPreviewUIView: UIView {
|
|
376
|
+
override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
|
|
377
|
+
|
|
378
|
+
var previewLayer: AVCaptureVideoPreviewLayer {
|
|
379
|
+
layer as! AVCaptureVideoPreviewLayer
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
override func layoutSubviews() {
|
|
383
|
+
super.layoutSubviews()
|
|
384
|
+
previewLayer.frame = bounds
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Usage
|
|
390
|
+
|
|
391
|
+
```swift
|
|
392
|
+
struct CameraScreen: View {
|
|
393
|
+
@State private var cameraManager = CameraManager()
|
|
394
|
+
|
|
395
|
+
var body: some View {
|
|
396
|
+
CameraPreview(session: cameraManager.session)
|
|
397
|
+
.ignoresSafeArea()
|
|
398
|
+
.task { await cameraManager.start() }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Gotchas
|
|
404
|
+
|
|
405
|
+
- **Use a custom UIView subclass with `layerClass`.** Overriding `layerClass` avoids adding a sublayer and ensures the preview layer resizes automatically with the view.
|
|
406
|
+
- **Session management belongs outside the representable.** Create and manage `AVCaptureSession` in a separate model. The representable only displays it.
|
|
407
|
+
- **Orientation.** Set `previewLayer.connection?.videoRotationAngle` if supporting device rotation.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 5. PHPickerViewController Wrapper
|
|
412
|
+
|
|
413
|
+
Multi-select photo picker that loads selected images asynchronously.
|
|
414
|
+
|
|
415
|
+
```swift
|
|
416
|
+
import SwiftUI
|
|
417
|
+
import PhotosUI
|
|
418
|
+
|
|
419
|
+
struct PhotoPicker: UIViewControllerRepresentable {
|
|
420
|
+
@Binding var selectedImages: [UIImage]
|
|
421
|
+
var selectionLimit: Int = 0 // 0 = unlimited
|
|
422
|
+
@Environment(\.dismiss) private var dismiss
|
|
423
|
+
|
|
424
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
425
|
+
|
|
426
|
+
func makeUIViewController(context: Context) -> PHPickerViewController {
|
|
427
|
+
var config = PHPickerConfiguration(photoLibrary: .shared())
|
|
428
|
+
config.filter = .images
|
|
429
|
+
config.selectionLimit = selectionLimit
|
|
430
|
+
config.preferredAssetRepresentationMode = .current
|
|
431
|
+
|
|
432
|
+
let picker = PHPickerViewController(configuration: config)
|
|
433
|
+
picker.delegate = context.coordinator
|
|
434
|
+
return picker
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
|
|
438
|
+
// Nothing to update -- configuration is immutable after creation
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
final class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
|
442
|
+
let parent: PhotoPicker
|
|
443
|
+
|
|
444
|
+
init(_ parent: PhotoPicker) { self.parent = parent }
|
|
445
|
+
|
|
446
|
+
func picker(
|
|
447
|
+
_ picker: PHPickerViewController,
|
|
448
|
+
didFinishPicking results: [PHPickerResult]
|
|
449
|
+
) {
|
|
450
|
+
parent.dismiss()
|
|
451
|
+
|
|
452
|
+
guard !results.isEmpty else { return }
|
|
453
|
+
|
|
454
|
+
Task { @MainActor in
|
|
455
|
+
var images: [UIImage] = []
|
|
456
|
+
for result in results {
|
|
457
|
+
if let image = await loadImage(from: result.itemProvider) {
|
|
458
|
+
images.append(image)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
parent.selectedImages = images
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private func loadImage(from provider: NSItemProvider) async -> UIImage? {
|
|
466
|
+
await withCheckedContinuation { continuation in
|
|
467
|
+
if provider.canLoadObject(ofClass: UIImage.self) {
|
|
468
|
+
provider.loadObject(ofClass: UIImage.self) { image, _ in
|
|
469
|
+
continuation.resume(returning: image as? UIImage)
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
continuation.resume(returning: nil)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Usage
|
|
481
|
+
|
|
482
|
+
```swift
|
|
483
|
+
struct ImagePickerDemo: View {
|
|
484
|
+
@State private var images: [UIImage] = []
|
|
485
|
+
@State private var showPicker = false
|
|
486
|
+
|
|
487
|
+
var body: some View {
|
|
488
|
+
VStack {
|
|
489
|
+
ScrollView(.horizontal) {
|
|
490
|
+
HStack {
|
|
491
|
+
ForEach(images.indices, id: \.self) { i in
|
|
492
|
+
Image(uiImage: images[i])
|
|
493
|
+
.resizable()
|
|
494
|
+
.scaledToFill()
|
|
495
|
+
.frame(width: 100, height: 100)
|
|
496
|
+
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
Button("Pick Photos") { showPicker = true }
|
|
501
|
+
}
|
|
502
|
+
.sheet(isPresented: $showPicker) {
|
|
503
|
+
PhotoPicker(selectedImages: $images, selectionLimit: 5)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Gotchas
|
|
510
|
+
|
|
511
|
+
- **Always dismiss in the delegate.** `picker(_:didFinishPicking:)` is called for both selection and cancellation (with empty results). Dismiss in both cases.
|
|
512
|
+
- **Async image loading.** `NSItemProvider.loadObject` is completion-based. Wrap in `withCheckedContinuation` for async/await usage. Load images after dismissal to avoid blocking the picker UI.
|
|
513
|
+
- **iOS 17 alternative.** `PhotosUI.PhotosPicker` is a native SwiftUI view. Prefer it unless you need custom picker UI or advanced filtering.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 6. MFMailComposeViewController Wrapper
|
|
518
|
+
|
|
519
|
+
Present the system email composer with pre-filled fields and handle the result.
|
|
520
|
+
|
|
521
|
+
```swift
|
|
522
|
+
import SwiftUI
|
|
523
|
+
import MessageUI
|
|
524
|
+
|
|
525
|
+
struct MailComposer: UIViewControllerRepresentable {
|
|
526
|
+
let subject: String
|
|
527
|
+
let recipients: [String]
|
|
528
|
+
let body: String
|
|
529
|
+
var isHTML: Bool = false
|
|
530
|
+
var onResult: ((MFMailComposeResult) -> Void)?
|
|
531
|
+
@Environment(\.dismiss) private var dismiss
|
|
532
|
+
|
|
533
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
534
|
+
|
|
535
|
+
func makeUIViewController(context: Context) -> MFMailComposeViewController {
|
|
536
|
+
let controller = MFMailComposeViewController()
|
|
537
|
+
controller.mailComposeDelegate = context.coordinator
|
|
538
|
+
controller.setSubject(subject)
|
|
539
|
+
controller.setToRecipients(recipients)
|
|
540
|
+
controller.setMessageBody(body, isHTML: isHTML)
|
|
541
|
+
return controller
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: Context) {
|
|
545
|
+
// Cannot update mail compose after presentation
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
final class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
|
|
549
|
+
let parent: MailComposer
|
|
550
|
+
|
|
551
|
+
init(_ parent: MailComposer) { self.parent = parent }
|
|
552
|
+
|
|
553
|
+
func mailComposeController(
|
|
554
|
+
_ controller: MFMailComposeViewController,
|
|
555
|
+
didFinishWith result: MFMailComposeResult,
|
|
556
|
+
error: Error?
|
|
557
|
+
) {
|
|
558
|
+
parent.onResult?(result)
|
|
559
|
+
parent.dismiss()
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Usage
|
|
566
|
+
|
|
567
|
+
```swift
|
|
568
|
+
struct FeedbackView: View {
|
|
569
|
+
@State private var showMail = false
|
|
570
|
+
|
|
571
|
+
var body: some View {
|
|
572
|
+
Button("Send Feedback") {
|
|
573
|
+
guard MFMailComposeViewController.canSendMail() else { return }
|
|
574
|
+
showMail = true
|
|
575
|
+
}
|
|
576
|
+
.sheet(isPresented: $showMail) {
|
|
577
|
+
MailComposer(
|
|
578
|
+
subject: "App Feedback",
|
|
579
|
+
recipients: ["support@example.com"],
|
|
580
|
+
body: "I have feedback about..."
|
|
581
|
+
) { result in
|
|
582
|
+
print("Mail result: \(result.rawValue)")
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Gotchas
|
|
590
|
+
|
|
591
|
+
- **Check `canSendMail()` before presenting.** The app crashes if `MFMailComposeViewController` is presented on a device with no mail account configured.
|
|
592
|
+
- **Cannot update after presentation.** `updateUIViewController` is intentionally empty -- the mail compose API does not support changing fields after the controller is shown.
|
|
593
|
+
- **The delegate protocol name is `MFMailComposeViewControllerDelegate`**, not `MFMailComposeDelegate`.
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 7. UIActivityViewController Wrapper (Share Sheet)
|
|
598
|
+
|
|
599
|
+
Present the system share sheet. This is a `UIViewControllerRepresentable` because `UIActivityViewController` is a controller, not a view.
|
|
600
|
+
|
|
601
|
+
```swift
|
|
602
|
+
import SwiftUI
|
|
603
|
+
|
|
604
|
+
struct ShareSheet: UIViewControllerRepresentable {
|
|
605
|
+
let items: [Any]
|
|
606
|
+
var activities: [UIActivity]? = nil
|
|
607
|
+
var excludedTypes: [UIActivity.ActivityType]? = nil
|
|
608
|
+
|
|
609
|
+
func makeUIViewController(context: Context) -> UIActivityViewController {
|
|
610
|
+
let controller = UIActivityViewController(
|
|
611
|
+
activityItems: items,
|
|
612
|
+
applicationActivities: activities
|
|
613
|
+
)
|
|
614
|
+
controller.excludedActivityTypes = excludedTypes
|
|
615
|
+
return controller
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
|
|
619
|
+
// Cannot update after presentation
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Usage
|
|
625
|
+
|
|
626
|
+
```swift
|
|
627
|
+
struct ContentView: View {
|
|
628
|
+
@State private var showShare = false
|
|
629
|
+
|
|
630
|
+
var body: some View {
|
|
631
|
+
Button("Share") { showShare = true }
|
|
632
|
+
.sheet(isPresented: $showShare) {
|
|
633
|
+
ShareSheet(items: ["Check out this app!", URL(string: "https://example.com")!])
|
|
634
|
+
.presentationDetents([.medium])
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Gotchas
|
|
641
|
+
|
|
642
|
+
- **Present via `.sheet`.** Do not try to use `UIActivityViewController` as an inline view -- it is a modal controller.
|
|
643
|
+
- **iPad requires `popoverPresentationController`.** When using on iPad outside of `.sheet`, set the source view/rect on the popover controller. SwiftUI's `.sheet` handles this automatically.
|
|
644
|
+
- **iOS 16+ alternative.** `ShareLink` is a native SwiftUI view for Transferable items. Prefer it for simple sharing.
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## 8. UISearchBar Wrapper
|
|
649
|
+
|
|
650
|
+
Wrap `UISearchBar` with delegate-based callbacks, debounce support, and cancel button handling.
|
|
651
|
+
|
|
652
|
+
```swift
|
|
653
|
+
import SwiftUI
|
|
654
|
+
import Combine
|
|
655
|
+
|
|
656
|
+
struct SearchBar: UIViewRepresentable {
|
|
657
|
+
@Binding var text: String
|
|
658
|
+
var placeholder: String = "Search"
|
|
659
|
+
var onSearch: ((String) -> Void)?
|
|
660
|
+
var onCancel: (() -> Void)?
|
|
661
|
+
|
|
662
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
663
|
+
|
|
664
|
+
func makeUIView(context: Context) -> UISearchBar {
|
|
665
|
+
let searchBar = UISearchBar()
|
|
666
|
+
searchBar.delegate = context.coordinator
|
|
667
|
+
searchBar.placeholder = placeholder
|
|
668
|
+
searchBar.searchBarStyle = .minimal
|
|
669
|
+
searchBar.autocapitalizationType = .none
|
|
670
|
+
return searchBar
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
func updateUIView(_ uiView: UISearchBar, context: Context) {
|
|
674
|
+
if uiView.text != text {
|
|
675
|
+
uiView.text = text
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
final class Coordinator: NSObject, UISearchBarDelegate {
|
|
680
|
+
var parent: SearchBar
|
|
681
|
+
private var debounceTask: Task<Void, Never>?
|
|
682
|
+
|
|
683
|
+
init(_ parent: SearchBar) { self.parent = parent }
|
|
684
|
+
|
|
685
|
+
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
|
686
|
+
parent.text = searchText
|
|
687
|
+
searchBar.showsCancelButton = !searchText.isEmpty
|
|
688
|
+
|
|
689
|
+
// Debounce search
|
|
690
|
+
debounceTask?.cancel()
|
|
691
|
+
debounceTask = Task { @MainActor in
|
|
692
|
+
try? await Task.sleep(for: .milliseconds(300))
|
|
693
|
+
guard !Task.isCancelled else { return }
|
|
694
|
+
parent.onSearch?(searchText)
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
|
699
|
+
debounceTask?.cancel()
|
|
700
|
+
parent.onSearch?(parent.text)
|
|
701
|
+
searchBar.resignFirstResponder()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
|
705
|
+
parent.text = ""
|
|
706
|
+
parent.onCancel?()
|
|
707
|
+
searchBar.resignFirstResponder()
|
|
708
|
+
searchBar.showsCancelButton = false
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Usage
|
|
715
|
+
|
|
716
|
+
```swift
|
|
717
|
+
struct SearchableList: View {
|
|
718
|
+
@State private var query = ""
|
|
719
|
+
@State private var results: [String] = []
|
|
720
|
+
|
|
721
|
+
var body: some View {
|
|
722
|
+
VStack(spacing: 0) {
|
|
723
|
+
SearchBar(text: $query, placeholder: "Search items") { text in
|
|
724
|
+
results = performSearch(text)
|
|
725
|
+
}
|
|
726
|
+
List(results, id: \.self) { Text($0) }
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Gotchas
|
|
733
|
+
|
|
734
|
+
- **Native `.searchable` modifier.** Prefer SwiftUI's `.searchable(text:)` modifier for standard search patterns. Use this wrapper only when you need precise control over search bar appearance or delegate timing.
|
|
735
|
+
- **Debounce with `Task.sleep`.** Cancel the previous task before starting a new one to debounce. `Combine` is not needed.
|
|
736
|
+
- **Cancel button state.** Toggle `showsCancelButton` in the delegate, not in `updateUIView`, to avoid layout jumps.
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## 9. SFSafariViewController Wrapper
|
|
741
|
+
|
|
742
|
+
Present an in-app browser using Safari's full rendering engine, reader mode, and content blocking.
|
|
743
|
+
|
|
744
|
+
```swift
|
|
745
|
+
import SwiftUI
|
|
746
|
+
import SafariServices
|
|
747
|
+
|
|
748
|
+
struct SafariView: UIViewControllerRepresentable {
|
|
749
|
+
let url: URL
|
|
750
|
+
var preferredBarTintColor: UIColor? = nil
|
|
751
|
+
var preferredControlTintColor: UIColor? = nil
|
|
752
|
+
var dismissButtonStyle: SFSafariViewController.DismissButtonStyle = .done
|
|
753
|
+
@Environment(\.dismiss) private var dismiss
|
|
754
|
+
|
|
755
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
756
|
+
|
|
757
|
+
func makeUIViewController(context: Context) -> SFSafariViewController {
|
|
758
|
+
let config = SFSafariViewController.Configuration()
|
|
759
|
+
config.entersReaderIfAvailable = false
|
|
760
|
+
config.barCollapsingEnabled = true
|
|
761
|
+
|
|
762
|
+
let safari = SFSafariViewController(url: url, configuration: config)
|
|
763
|
+
safari.delegate = context.coordinator
|
|
764
|
+
safari.preferredBarTintColor = preferredBarTintColor
|
|
765
|
+
safari.preferredControlTintColor = preferredControlTintColor
|
|
766
|
+
safari.dismissButtonStyle = dismissButtonStyle
|
|
767
|
+
return safari
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
|
|
771
|
+
// SFSafariViewController does not support URL changes after creation.
|
|
772
|
+
// To navigate to a new URL, dismiss and re-present with a new URL.
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
final class Coordinator: NSObject, SFSafariViewControllerDelegate {
|
|
776
|
+
let parent: SafariView
|
|
777
|
+
|
|
778
|
+
init(_ parent: SafariView) { self.parent = parent }
|
|
779
|
+
|
|
780
|
+
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
781
|
+
parent.dismiss()
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Usage
|
|
788
|
+
|
|
789
|
+
```swift
|
|
790
|
+
struct ArticleView: View {
|
|
791
|
+
@State private var showSafari = false
|
|
792
|
+
let articleURL = URL(string: "https://example.com/article")!
|
|
793
|
+
|
|
794
|
+
var body: some View {
|
|
795
|
+
Button("Read Full Article") { showSafari = true }
|
|
796
|
+
.sheet(isPresented: $showSafari) {
|
|
797
|
+
SafariView(url: articleURL)
|
|
798
|
+
.ignoresSafeArea()
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### Gotchas
|
|
805
|
+
|
|
806
|
+
- **Cannot change URL after creation.** `SFSafariViewController` does not expose its navigation. To load a different URL, dismiss and present a new instance.
|
|
807
|
+
- **Must use `.sheet` or fullScreenCover.** Apple rejects apps that embed `SFSafariViewController` as a child view controller inline -- it must be presented modally.
|
|
808
|
+
- **The dismiss delegate is essential.** Without `safariViewControllerDidFinish`, the Done button tap does not dismiss the SwiftUI sheet, leaving the user stuck.
|
|
809
|
+
- **`ignoresSafeArea()`.** Safari's own chrome handles safe areas. Without this modifier, you get double safe area insets.
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## 10. PDFView Wrapper (PDFKit)
|
|
814
|
+
|
|
815
|
+
Display PDF documents in SwiftUI using `PDFView` from PDFKit. Supports loading from URL, Data, or file path, with configurable display mode and auto-scaling.
|
|
816
|
+
|
|
817
|
+
```swift
|
|
818
|
+
import SwiftUI
|
|
819
|
+
import PDFKit
|
|
820
|
+
|
|
821
|
+
struct PDFViewer: UIViewRepresentable {
|
|
822
|
+
let document: PDFDocument?
|
|
823
|
+
var displayMode: PDFDisplayMode = .singlePageContinuous
|
|
824
|
+
var autoScales: Bool = true
|
|
825
|
+
var displayDirection: PDFDisplayDirection = .vertical
|
|
826
|
+
var pageShadowsEnabled: Bool = true
|
|
827
|
+
|
|
828
|
+
func makeUIView(context: Context) -> PDFView {
|
|
829
|
+
let pdfView = PDFView()
|
|
830
|
+
pdfView.displayMode = displayMode
|
|
831
|
+
pdfView.displayDirection = displayDirection
|
|
832
|
+
pdfView.autoScales = autoScales
|
|
833
|
+
pdfView.pageShadowsEnabled = pageShadowsEnabled
|
|
834
|
+
pdfView.document = document
|
|
835
|
+
return pdfView
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
func updateUIView(_ uiView: PDFView, context: Context) {
|
|
839
|
+
// Update document if it changed (reference comparison)
|
|
840
|
+
if uiView.document !== document {
|
|
841
|
+
uiView.document = document
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if uiView.displayMode != displayMode {
|
|
845
|
+
uiView.displayMode = displayMode
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if uiView.autoScales != autoScales {
|
|
849
|
+
uiView.autoScales = autoScales
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Convenience Initializers
|
|
856
|
+
|
|
857
|
+
```swift
|
|
858
|
+
extension PDFViewer {
|
|
859
|
+
/// Load a PDF from a URL (local file or remote).
|
|
860
|
+
init(url: URL, displayMode: PDFDisplayMode = .singlePageContinuous) {
|
|
861
|
+
self.document = PDFDocument(url: url)
|
|
862
|
+
self.displayMode = displayMode
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/// Load a PDF from raw data.
|
|
866
|
+
init(data: Data, displayMode: PDFDisplayMode = .singlePageContinuous) {
|
|
867
|
+
self.document = PDFDocument(data: data)
|
|
868
|
+
self.displayMode = displayMode
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Usage
|
|
874
|
+
|
|
875
|
+
```swift
|
|
876
|
+
struct DocumentView: View {
|
|
877
|
+
let pdfURL: URL
|
|
878
|
+
|
|
879
|
+
var body: some View {
|
|
880
|
+
PDFViewer(url: pdfURL)
|
|
881
|
+
.ignoresSafeArea(edges: .bottom)
|
|
882
|
+
.navigationTitle("Document")
|
|
883
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### With Async Loading
|
|
889
|
+
|
|
890
|
+
```swift
|
|
891
|
+
struct RemotePDFView: View {
|
|
892
|
+
let url: URL
|
|
893
|
+
@State private var document: PDFDocument?
|
|
894
|
+
@State private var isLoading = true
|
|
895
|
+
@State private var errorMessage: String?
|
|
896
|
+
|
|
897
|
+
var body: some View {
|
|
898
|
+
Group {
|
|
899
|
+
if let document {
|
|
900
|
+
PDFViewer(document: document)
|
|
901
|
+
} else if isLoading {
|
|
902
|
+
ProgressView("Loading PDF...")
|
|
903
|
+
} else if let errorMessage {
|
|
904
|
+
ContentUnavailableView(
|
|
905
|
+
"Could Not Load PDF",
|
|
906
|
+
systemImage: "doc.text.fill",
|
|
907
|
+
description: Text(errorMessage)
|
|
908
|
+
)
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
.task {
|
|
912
|
+
do {
|
|
913
|
+
let (data, _) = try await URLSession.shared.data(from: url)
|
|
914
|
+
document = PDFDocument(data: data)
|
|
915
|
+
} catch {
|
|
916
|
+
errorMessage = error.localizedDescription
|
|
917
|
+
}
|
|
918
|
+
isLoading = false
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
### PDFView with Page Navigation
|
|
925
|
+
|
|
926
|
+
```swift
|
|
927
|
+
struct NavigablePDFView: UIViewRepresentable {
|
|
928
|
+
let document: PDFDocument?
|
|
929
|
+
@Binding var currentPageIndex: Int
|
|
930
|
+
|
|
931
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
932
|
+
|
|
933
|
+
func makeUIView(context: Context) -> PDFView {
|
|
934
|
+
let pdfView = PDFView()
|
|
935
|
+
pdfView.displayMode = .singlePageContinuous
|
|
936
|
+
pdfView.autoScales = true
|
|
937
|
+
pdfView.document = document
|
|
938
|
+
|
|
939
|
+
NotificationCenter.default.addObserver(
|
|
940
|
+
context.coordinator,
|
|
941
|
+
selector: #selector(Coordinator.pageChanged(_:)),
|
|
942
|
+
name: .PDFViewPageChanged,
|
|
943
|
+
object: pdfView
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
return pdfView
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
func updateUIView(_ uiView: PDFView, context: Context) {
|
|
950
|
+
if uiView.document !== document {
|
|
951
|
+
uiView.document = document
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Navigate to page if binding changed externally
|
|
955
|
+
if let doc = uiView.document,
|
|
956
|
+
let page = doc.page(at: currentPageIndex),
|
|
957
|
+
uiView.currentPage != page {
|
|
958
|
+
uiView.go(to: page)
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
static func dismantleUIView(_ uiView: PDFView, coordinator: Coordinator) {
|
|
963
|
+
NotificationCenter.default.removeObserver(coordinator)
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
final class Coordinator: NSObject {
|
|
967
|
+
var parent: NavigablePDFView
|
|
968
|
+
|
|
969
|
+
init(_ parent: NavigablePDFView) { self.parent = parent }
|
|
970
|
+
|
|
971
|
+
@objc func pageChanged(_ notification: Notification) {
|
|
972
|
+
guard let pdfView = notification.object as? PDFView,
|
|
973
|
+
let currentPage = pdfView.currentPage,
|
|
974
|
+
let document = pdfView.document else { return }
|
|
975
|
+
let index = document.index(for: currentPage)
|
|
976
|
+
if parent.currentPageIndex != index {
|
|
977
|
+
parent.currentPageIndex = index
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### Gotchas
|
|
985
|
+
|
|
986
|
+
- **`PDFView` inherits from `UIView`.** Use `UIViewRepresentable`, not `UIViewControllerRepresentable`.
|
|
987
|
+
- **Document is a reference type.** Use `!==` for identity comparison in `updateUIView` to avoid unnecessary reloads.
|
|
988
|
+
- **Page change notifications.** Use `NotificationCenter` with `.PDFViewPageChanged` -- `PDFView` does not use a delegate pattern for page changes.
|
|
989
|
+
- **Remove observers in `dismantleUIView`.** Failing to remove `NotificationCenter` observers causes crashes after the view is removed.
|
|
990
|
+
- **`autoScales`** fits the PDF to the view width. Disable it if you want the user to start at a specific zoom level.
|
|
991
|
+
- **Thread safety.** `PDFDocument` loading can be expensive. Load asynchronously and assign on the main thread.
|
|
992
|
+
|
|
993
|
+
> **Docs:** [PDFView](https://sosumi.ai/documentation/pdfkit/pdfview) | [PDFKit](https://sosumi.ai/documentation/pdfkit)
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
997
|
+
## 11. MFMessageComposeViewController Wrapper
|
|
998
|
+
|
|
999
|
+
Present the system SMS/MMS composer with pre-filled recipients, body, and optional attachments. Companion to Recipe 6 (MFMailComposeViewController).
|
|
1000
|
+
|
|
1001
|
+
```swift
|
|
1002
|
+
import SwiftUI
|
|
1003
|
+
import MessageUI
|
|
1004
|
+
|
|
1005
|
+
struct MessageComposer: UIViewControllerRepresentable {
|
|
1006
|
+
let recipients: [String]
|
|
1007
|
+
let body: String
|
|
1008
|
+
var attachments: [MessageAttachment] = []
|
|
1009
|
+
var onResult: ((MessageComposeResult) -> Void)?
|
|
1010
|
+
@Environment(\.dismiss) private var dismiss
|
|
1011
|
+
|
|
1012
|
+
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
|
1013
|
+
|
|
1014
|
+
func makeUIViewController(context: Context) -> MFMessageComposeViewController {
|
|
1015
|
+
let controller = MFMessageComposeViewController()
|
|
1016
|
+
controller.messageComposeDelegate = context.coordinator
|
|
1017
|
+
controller.recipients = recipients
|
|
1018
|
+
controller.body = body
|
|
1019
|
+
|
|
1020
|
+
for attachment in attachments {
|
|
1021
|
+
controller.addAttachmentData(
|
|
1022
|
+
attachment.data,
|
|
1023
|
+
typeIdentifier: attachment.typeIdentifier,
|
|
1024
|
+
filename: attachment.filename
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return controller
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
func updateUIViewController(
|
|
1032
|
+
_ uiViewController: MFMessageComposeViewController,
|
|
1033
|
+
context: Context
|
|
1034
|
+
) {
|
|
1035
|
+
// Cannot update message compose after presentation
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
final class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
|
|
1039
|
+
let parent: MessageComposer
|
|
1040
|
+
|
|
1041
|
+
init(_ parent: MessageComposer) { self.parent = parent }
|
|
1042
|
+
|
|
1043
|
+
func messageComposeViewController(
|
|
1044
|
+
_ controller: MFMessageComposeViewController,
|
|
1045
|
+
didFinishWith result: MessageComposeResult
|
|
1046
|
+
) {
|
|
1047
|
+
parent.onResult?(result)
|
|
1048
|
+
parent.dismiss()
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
struct MessageAttachment {
|
|
1054
|
+
let data: Data
|
|
1055
|
+
let typeIdentifier: String // UTI, e.g., "public.jpeg"
|
|
1056
|
+
let filename: String
|
|
1057
|
+
}
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
### Usage
|
|
1061
|
+
|
|
1062
|
+
```swift
|
|
1063
|
+
struct InviteView: View {
|
|
1064
|
+
@State private var showMessage = false
|
|
1065
|
+
|
|
1066
|
+
var body: some View {
|
|
1067
|
+
Button("Send Invite via SMS") {
|
|
1068
|
+
guard MFMessageComposeViewController.canSendText() else { return }
|
|
1069
|
+
showMessage = true
|
|
1070
|
+
}
|
|
1071
|
+
.sheet(isPresented: $showMessage) {
|
|
1072
|
+
MessageComposer(
|
|
1073
|
+
recipients: ["+1234567890"],
|
|
1074
|
+
body: "Join me on this app!"
|
|
1075
|
+
) { result in
|
|
1076
|
+
switch result {
|
|
1077
|
+
case .sent:
|
|
1078
|
+
print("Message sent")
|
|
1079
|
+
case .cancelled:
|
|
1080
|
+
print("User cancelled")
|
|
1081
|
+
case .failed:
|
|
1082
|
+
print("Message failed")
|
|
1083
|
+
@unknown default:
|
|
1084
|
+
break
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### With Image Attachment
|
|
1093
|
+
|
|
1094
|
+
```swift
|
|
1095
|
+
struct SharePhotoView: View {
|
|
1096
|
+
@State private var showMessage = false
|
|
1097
|
+
let image: UIImage
|
|
1098
|
+
|
|
1099
|
+
var body: some View {
|
|
1100
|
+
Button("Send Photo") {
|
|
1101
|
+
guard MFMessageComposeViewController.canSendText(),
|
|
1102
|
+
MFMessageComposeViewController.canSendAttachments() else {
|
|
1103
|
+
return
|
|
1104
|
+
}
|
|
1105
|
+
showMessage = true
|
|
1106
|
+
}
|
|
1107
|
+
.sheet(isPresented: $showMessage) {
|
|
1108
|
+
MessageComposer(
|
|
1109
|
+
recipients: [],
|
|
1110
|
+
body: "Check out this photo!",
|
|
1111
|
+
attachments: [
|
|
1112
|
+
MessageAttachment(
|
|
1113
|
+
data: image.jpegData(compressionQuality: 0.8) ?? Data(),
|
|
1114
|
+
typeIdentifier: "public.jpeg",
|
|
1115
|
+
filename: "photo.jpg"
|
|
1116
|
+
)
|
|
1117
|
+
]
|
|
1118
|
+
)
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### Gotchas
|
|
1125
|
+
|
|
1126
|
+
- **Check `canSendText()` before presenting.** The app crashes if `MFMessageComposeViewController` is presented on a device that cannot send texts (e.g., iPod touch without iMessage).
|
|
1127
|
+
- **Check `canSendAttachments()` before adding attachments.** Not all devices or carriers support MMS attachments.
|
|
1128
|
+
- **The delegate protocol is `MFMessageComposeViewControllerDelegate`**, not `MFMessageComposeDelegate`. It has a single required method.
|
|
1129
|
+
- **Cannot update after presentation.** Like `MFMailComposeViewController`, the message composer API does not support changing fields after the controller is shown.
|
|
1130
|
+
- **iMessage vs. SMS.** The controller automatically uses iMessage when available. You cannot force one protocol over the other.
|
|
1131
|
+
- **Simulator limitation.** `canSendText()` returns `false` on the simulator. Test on a physical device.
|
|
1132
|
+
|
|
1133
|
+
> **Docs:** [MFMessageComposeViewController](https://sosumi.ai/documentation/messageui/mfmessagecomposeviewcontroller) | [MFMessageComposeViewControllerDelegate](https://sosumi.ai/documentation/messageui/mfmessagecomposeviewcontrollerdelegate)
|