| State & UI | | |
| New UI screens | SwiftUI | UIKit interop only where Apple APIs require it |
| Observable state (iOS 17+) | @Observable + main actor isolation | Replaces ObservableObject/Published |
| Async work | async/await, structured concurrency | Detached tasks only when intentionally breaking inheritance |
| Unit/integration tests | Swift Testing | Preferred over XCTest for new tests |
| UI automation tests | XCTest / XCUITest | Keep using for UI and performance tests |
| State machine discipline | | |
| Submit guard | guard state == .idle else { return } | Prevents double-tap duplicate submissions in @Observable stores |
| Transient completion | One-shot UI effect or explicit acknowledgement | Do not reset durable state on an arbitrary timer; store and cancel any delayed task |
| Minimal state enums | Remove states that can't happen anymore | Dead enum cases produce dead error handling and mislead future readers |
| Networking & resilience | | |
| Network reachability | @Observable singleton + NWPathMonitor | Publish isConnected; disable submit buttons when offline; start monitor in screen .onAppear |
| Agent tooling & build | | |
| Agent tooling (in Xcode) | Xcode native assistant | Verify the live stable/beta Xcode and Swift versions, and Xcode's macOS host requirement (matters for CI runner images), at developer.apple.com/news/releases before citing one; check Apple's Upcoming Requirements page for the upload-SDK floor and minimum deployment target before submission — do not build release submissions against a beta SDK |
| Agent tooling (outside Xcode) | XcodeBuildMCP if callable | Otherwise fall back immediately to Apple CLI |
| CLI fallback | xcodebuild, simctl, xcresulttool | Default path when MCP is unavailable or blocked |
| Build / install / stale-app failures | software-ios-runtime-debugging | Use before UI or feature diagnosis |
| XcodeGen projects | scripts/generate-xcodeproj.sh | Must regenerate after adding new Swift files |
| Local-dev launcher pair | scripts/run-local-ios-dev.sh + scripts/stop-local-ios-dev.sh | Two defensive guards (grep env + generated plist for localhost:); persist simulator UDID; never pkill -f Simulator. See quick-reference-extended.md#agent-tooling--build |
.pbxproj-managed projects | Add new files to target membership | Do not assume on-disk Swift files are auto-discovered |
| Generated files / CI landmines | Commit generated outputs or regenerate in CI hook | git add -f for gitignored-folder tracked files; Xcode Cloud ci_post_clone.sh for env shims. See quick-reference-extended.md |
| TARGETED_DEVICE_FAMILY → "1" | Requires fresh archive + ASC build attachment | Changing project.yml alone is insufficient; value is baked into binary. |
| Canvas, immersive screens, sheets, l10n hygiene | See quick-reference-extended.md | Canvas start state and gestures, persistent sheets, viz state ownership, grid sizing, sheet swapping, l10n plain-enum call rule (catalog parity and regeneration live in software-localisation) |
| StoreKit & billing | | |
| StoreKit 2 subscriptions | @Observable StoreKitManager + TransactionSyncService | transaction.finish() only after backend sync confirms |
| Paywall presentation | .sheet(isPresented:) from any locked screen | Don't navigate to Settings; present modal directly |
| Product pricing display | product.displayPrice from StoreKit | Never hardcode prices; Apple handles locale formatting |
| Promotional offers (rewards) | Server-signed .promotionalOffer() in Product.purchase(options:) | Bridges Stripe credit gaps for Apple-billed users; user must redeem |
| Paid Apps Agreement (#1 invisible blocker) | Verify Active at appstoreconnect.apple.com/business | Silent empty Product.products(for:) results; check before subscription-level diagnosis. Full decision table + timeline in app-store-connect-checklist.md Phase 5. See quick-reference-extended.md |
ASC Missing Metadata | Fill all required fields: prices, localization, review screenshot | Products exist but fields incomplete. Workflow in app-store-connect-checklist.md. See quick-reference-extended.md |
| Review screenshot | sRGB 8-bit RGB PNG 72 DPI, 1284 × 2778 | ASC rejects Display P3 / 16-bit device screenshots; use Pillow profile conversion. See quick-reference-extended.md |
| Accounting currency (backend-denominated) | NumberFormatter + explicit currencyCode + locale = .current | Never String(format: "£%.2f", …); details in quick-reference-extended.md |
| Year/ID display | Text(verbatim:) | Suppress locale number formatting (2026 not 2,026) |
| Locale-aware time formatting; strings and backend locale | "jm" date skeleton via DateFormatter; strings, plurals and the backend translation pipeline live in software-localisation | Platform rows in quick-reference-extended.md |
| Auth & push | | |
| Sign in with Apple | ASAuthorizationController + CheckedContinuation | Guideline 4.8 requires an equivalent privacy-preserving login (limits data to name/email, allows a private relay email, no ad tracking without consent) if a 3rd-party social login is offered — Sign in with Apple is the usual way to satisfy it, not the only one; check current exemptions |
| OTP code input | Hidden TextField + .textContentType(.oneTimeCode) | Better than magic links for native; iOS auto-fills from notifications |
Non-@MainActor delegates | Match the SDK requirement; hop to @MainActor for UI state | MainActor.assumeIsolated checks isolation; use it only when the callback contract guarantees that executor. |
| Push notification categories | Register UNNotificationCategory in didFinishLaunchingWithOptions | Must be set before any notification arrives; match aps.category from backend |
| Badge count (iOS 16+) | try? await UNUserNotificationCenter.current().setBadgeCount(0) | applicationIconBadgeNumber is deprecated; setBadgeCount is async throws |
| Push delegate isolation | Inspect the imported SDK requirement and callback contract | Hop UI mutations to @MainActor; do not infer a universal delegate fix from a private crash symbol. See swiftui-observation-concurrency.md and quick-reference-extended.md |
Task { } isolation | Inherits the actor context where its closure is formed; annotate explicitly across nonisolated callbacks | Task.detached breaks actor inheritance. See swiftui-observation-concurrency.md |
actor vs @MainActor final class | Choose the isolation owner by the work and shared state | A main-actor caller resumes on the main actor after await; keep CPU-heavy work off it. See swiftui-observation-concurrency.md |
@Sendable async closure awaited from @MainActor | Return results to the isolated caller; isolate the body if it needs UI state | @Sendable is a transfer-safety constraint, not an executor choice; caller isolation survives await. See swiftui-observation-concurrency.md |
SCNView in UIViewRepresentable | Create initial scene once; update changed inputs | A no-op updateUIView is correct only for immutable inputs. See swiftui-observation-concurrency.md |
| Push action routing | Check response.actionIdentifier in didReceive | UNNotificationDefaultActionIdentifier = tap; custom IDs = action buttons; dismiss = no route |
| Push-open ownership, preferences, entitlements, APNs proof, export gate, backend routing, QA loop | See full rows in quick-reference-extended.md | Inspect final exported app/profile: push-enabled TestFlight/App Store exports require production; backend: per-device push_environment column authoritative |
| Swift Concurrency crash triage | Symptom-first triage runbook | Private SwiftUI symbol crash → start at swift-concurrency-crash-triage.md; ladder: console → MTC → TSan → lldb bt |
| SwiftUI API modernization | | |
| Deprecated API review | references/swiftui-deprecated-api.md | Systematic deprecated→modern mapping |
| SwiftUI performance audit | references/swiftui-performance.md | View splitting, lazy stacks, modifier efficiency |
| Modern Swift idioms | references/modern-swift-patterns.md | Foundation modernization, date/string/collection patterns |
| Concurrency (constructive) | | |
| Writing correct concurrency | references/swift-concurrency-patterns.md | Structured concurrency, async streams, bridging, migration |
| Concurrency compiler errors | references/swift-concurrency-diagnostics.md | Error→fix mapping for Swift 6 diagnostics |
| Persistence | | |
| SwiftData modeling | references/swiftdata-core.md | Core rules, predicates, CloudKit, indexing, class inheritance |
| Core Data persistence | references/core-data-persistence.md | Stack setup, contexts, object IDs, batch ops, migrations, CloudKit |
| Reusable app skeleton | references/native-ios-app-foundation-skeleton.md | SwiftUI shell, Observation state, persistence, CloudKit, App Intents, local AI hooks, release gates |
| iCloud database app | references/icloud-cloudkit-app-skeleton.md | SwiftData/Core Data/CloudKit choice, private/public/shared scopes, no-server limits |
| Stacks & monetization | | |
| Pick a starter stack to monetize/engage | references/starter-stacks-and-monetization.md | CloudKit→Cloudflare→RevenueCat→Supabase graduation ladder; on-device AI as free tier; webhook idempotency traps |
| Run iOS dev as a conveyor / app factory | references/ios-app-conveyor.md | 4 pillars: default stack per class, shared SPM, Fastlane+Match CI, agent build loop; 2026 stack survey |
| Build performance | | |
| Xcode build optimization | references/xcode-build-optimization.md | Benchmarking, diagnostic flags, SPM analysis, common wins |
| Swift 6.2+ / Xcode 26+ additions | See quick-reference-extended.md | Default MainActor isolation, iOS 26 withAnimation regression, stale SubscriptionStatus.all, AnyView/@ObservedObject/NavigationView/.id(UUID()) modernization, Combine lifecycle, coordinator navigation |