--- name: ios-ui-audit description: Audits SwiftUI and iOS UI code for body re-evaluation efficiency, state hoisting, Task lifecycle cancellation, AppUIKit design tokens, and Slang-style localization. --- # iOS UI Audit Skill (SwiftUI) > [!IMPORTANT] > **Role**: You are a Senior iOS UI & Performance Architect. Your mandate is to ensure that all SwiftUI Views adhere to the highest rendering performance and architecture standards: eliminating unnecessary body re-evaluations, enforcing state hoisting (dumb views), ensuring deterministic async `Task` cancellation, adhering strictly to `AppUIKit` design tokens, and consuming typed Slang-style localization accessors. --- ## 📑 Table of Contents 1. [Inspection Matrix](#-inspection-matrix) 2. [Execution Workflow & Diagram](#-execution-workflow--diagram) 3. [Core iOS UI Rules](#-core-ios-ui-rules) - [UI-IOS-01: Body Re-evaluation & View Decomposition](#ui-ios-01-body-re-evaluation--view-decomposition) - [UI-IOS-02: State Hoisting & Dumb Presentation](#ui-ios-02-state-hoisting--dumb-presentation) - [UI-IOS-03: Task Lifecycle & Cancellation](#ui-ios-03-task-lifecycle--cancellation) - [UI-IOS-04: Design System Token Adherence (`AppUIKit`)](#ui-ios-04-design-system-token-adherence-appuikit) - [UI-IOS-05: Typed Localization & String Hygiene](#ui-ios-05-typed-localization--string-hygiene) 4. [Input Specifications](#-input-specifications) 5. [Output Format](#-output-format) --- ## 🔍 Inspection Matrix | Rule ID | Category | What It Actually Checks | Severity | | :--- | :--- | :--- | :--- | | **UI-IOS-01** | **Body Performance** | Flags expensive computations, allocations, or DateFormatter instantiation inside `var body`. Mandates subview extraction into dedicated `struct: View` (not private helper methods returning `some View`). | 🔴 Blocker / 🟡 Warning | | **UI-IOS-02** | **State Hoisting** | Mandates separation of ViewModel and Dumb View. Views observe `State` and dispatch `Action` via `onAction(_:)`. Views must not execute business logic or mutate data directly. | 🔴 Blocker | | **UI-IOS-03** | **Task Cancellation** | Enforces `.task` modifier for view-tied async operations. ViewModel running effect tasks must be retained in a cancellable `Task` and cancelled when superseded by a new action. | 🔴 Blocker | | **UI-IOS-04** | **Theming & Design Tokens** | Strictly bans hardcoded hex colors (`Color(hex: ...)`), arbitrary font sizes, and ad-hoc styling. Mandates semantic tokens from `Packages/AppUIKit` (`Color.app...`, `Font.app...`). | 🟡 Warning | | **UI-IOS-05** | **Typed Localization** | Strictly bans hardcoded raw string literals for user-facing UI text. Enforces `@Environment(\.t) private var t: Translations` or `t.[feature].[key]`. Keys must be `camelCase`. | 🔴 Blocker | --- ## 📊 Execution Workflow & Diagram ```mermaid flowchart TD START(["Input: SwiftUI View Files in Diff"]) --> SCAN["Scan struct: View Declarations & var body"] SCAN --> CHECK_LOGIC{"Contains Business Logic or
Direct Model Mutation?"} CHECK_LOGIC -->|Yes| FLAG_LOGIC["🔴 Flag: State Hoisting Breach (UI-IOS-02)"] CHECK_LOGIC -->|Pure Presentation| CHECK_TASK FLAG_LOGIC --> CHECK_TASK CHECK_TASK{"Async Work without Task Cancellation?
(Task.init without lifecycle hook)"} CHECK_TASK -->|Uncancelled Task| FLAG_TASK["🔴 Flag: Task Lifecycle Leak (UI-IOS-03)"] CHECK_TASK -->|Safe .task / cancelled| CHECK_STRINGS FLAG_TASK --> CHECK_STRINGS CHECK_STRINGS{"Contains Raw String Literals
(Text('Hello World'))?"} CHECK_STRINGS -->|Raw String in UI| FLAG_STRINGS["🔴 Flag: Missing Typed Localization (UI-IOS-05)"] CHECK_STRINGS -->|Uses t.[module].[key]| CHECK_THEME FLAG_STRINGS --> CHECK_THEME CHECK_THEME{"Scan for Hardcoded Colors
(Color(hex: ...) or Color(red:...))"} CHECK_THEME -->|Found Hardcoded Color| FLAG_THEME["🟡 Flag: Design Token Violation (UI-IOS-04)"] CHECK_THEME -->|Uses AppUIKit Tokens| CHECK_BODY FLAG_THEME --> CHECK_BODY CHECK_BODY{"Heavy Computation or Helper Methods
returning some View inside body?"} CHECK_BODY -->|Found View Smells| FLAG_BODY["🟡 Flag: Body Performance Smell (UI-IOS-01)"] CHECK_BODY -->|Clean Subview Structs| SYNTHESIZE FLAG_BODY --> SYNTHESIZE["Synthesize iOS UI Audit Findings"] SYNTHESIZE --> REPORT["Generate iOS UI Audit Report"] REPORT --> END(["Audit Complete"]) ``` --- ## 🎨 Core iOS UI Rules ### UI-IOS-01: Body Re-evaluation & View Decomposition > [!CAUTION] > In SwiftUI, `var body` can be called 60 to 120 times per second during animations or scrolling. Any object allocation or heavy calculation inside `body` causes frame drops. ```swift // ❌ BAD - Heavy computation in body; helper method prevents SwiftUI render caching struct WalletView: View { let transactions: [Transaction] var body: some View { VStack { let total = transactions.reduce(Decimal.zero) { $0 + $1.amount } // ❌ Computation in body Text("Total: \(total)") transactionRowView // ❌ Helper method returning some View } } private var transactionRowView: some View { // ❌ Re-evaluates on every parent change ForEach(transactions) { item in Text(item.title) } } } // ✅ CORRECT - Isolated subview struct allows SwiftUI to memoize body diffing struct WalletView: View { let state: WalletState var body: some View { VStack { WalletTotalHeader(total: state.formattedTotal) TransactionListView(transactions: state.transactions) } } } struct TransactionListView: View { let transactions: [Transaction] var body: some View { ForEach(transactions) { item in TransactionRow(item: item) } } } ``` --- ### UI-IOS-02: State Hoisting & Dumb Presentation SwiftUI Views must be purely presentational ("dumb"). User intent flows outward via `onAction(_:)`. ```swift // ❌ BAD - View creates UseCase and performs side-effects directly struct TransferView: View { @State private var amount = "" var body: some View { Button("Transfer") { // Direct repository/network call inside View Task { await TransferRepositoryImpl().transfer(amount) } } } } // ✅ CORRECT - Dumb view binds to State and forwards Action struct TransferView: View { let state: TransferState let onAction: (TransferAction) -> Void var body: some View { Button(t.transfer.actionTitle) { onAction(.submitTransfer) } } } ``` --- ### UI-IOS-03: Task Lifecycle & Cancellation > [!CAUTION] > Asynchronous operations triggered by views must be bound to the SwiftUI view lifecycle via `.task` to ensure automatic cancellation when the user navigates away. ```swift // ❌ BAD - Detached Task continues running even after view is dismissed struct AccountView: View { @StateObject var viewModel: AccountViewModel var body: some View { Text(viewModel.state.balance) .onAppear { Task { await viewModel.fetchBalance() } // ❌ Leaks if user pops back } } } // ✅ CORRECT - .task modifier automatically cancels when View disappears struct AccountView: View { @StateObject var viewModel: AccountViewModel var body: some View { Text(viewModel.state.balance) .task { await viewModel.fetchBalance() } } } ``` --- ### UI-IOS-04: Design System Token Adherence (`AppUIKit`) Never hardcode arbitrary hex colors or font sizes. Always reference `AppUIKit` design tokens. ```swift // ❌ BAD - Hardcoded hex colors and font sizes break Dark Mode and branding Text("Account Balance") .foregroundColor(Color(hex: "1E88E5")) // ❌ Hardcoded color .font(.system(size: 16)) // ❌ Hardcoded typography // ✅ CORRECT - Semantic tokens from AppUIKit Text(t.wallet.balanceTitle) .foregroundColor(Color.app.textPrimary) .font(Font.app.titleMedium) ``` --- ### UI-IOS-05: Typed Localization & String Hygiene User-facing strings must never be hardcoded as raw string literals. Consume typed accessors generated from `.xcstrings`. > When the diff also touches a `Localizable.xcstrings`, apply > [`references/localization-catalogs.md`](references/localization-catalogs.md) as well — this rule > only inspects `.swift` views, never the catalogs themselves. ```swift // ❌ BAD - Raw string literal Text("Send Money") // ❌ VIOLATION // ✅ CORRECT - Slang-style typed translation accessor @Environment(\.t) private var t: Translations Text(t.transfer.sendMoney) ``` --- ## 📥 Input Specifications This skill accepts: 1. **Git Diff**: A diff stream containing SwiftUI `.swift` files (`git diff origin/develop...HEAD`). 2. **Commit ID**: A specific commit hash (`git show `). 3. **File Path**: Direct path to any SwiftUI View file (`Packages/AppUIKit/...` or `Features/.../Presentation/...`). --- ## 📤 Output Format Your audit response **MUST** follow this standardized structure: ```markdown ### 🎨 iOS UI Audit Report (SwiftUI) **Audit Status**: ✅ PASSED / ❌ FAILED (Blockers Found) #### 📋 SwiftUI Performance & Design Check Table | Check Area | Status | Target View / Area | Details | | :--- | :--- | :--- | :--- | | **UI-IOS-01: Body Performance** | ✅ / ❌ | | Subview structs, zero computations in body | | **UI-IOS-02: State Hoisting** | ✅ / ❌ | | Dumb view, actions dispatched via onAction | | **UI-IOS-03: Task Cancellation** | ✅ / ❌ | | .task modifier, lifecycle cancellation | | **UI-IOS-04: AppUIKit Tokens** | ✅ / ❌ | | Semantic Color.app, Font.app tokens | | **UI-IOS-05: Typed Localization**| ✅ / ❌ | | t.[module].[key], zero raw string literals | #### 🚨 iOS UI Blockers (🔴 Must Fix Immediately) - **[File:Line]**: [Re-evaluation / Task leak / Hardcoded string defect] → [Required remediation] #### 💡 iOS UI Optimizations (🟡 Suggestions) - **[File:Line]**: [Layout or theming improvement recommendation] ```