swift-ui-architect

Original🇺🇸 English
Translated

Opinionated SwiftUI architecture enforcement for iOS 17+ apps using Clean MVVM + Coordinator pattern. Enforces Airbnb's @Equatable diffing, @Observable state, NavigationStack coordinators, and Clean Architecture layer boundaries. This skill should be used when writing, reviewing, or refactoring SwiftUI code. Triggers on tasks involving SwiftUI views, ViewModels, navigation, state management, dependency injection, or iOS app architecture.

10installs
Added on

NPX Install

npx skill4agent add pproenca/dot-skills swift-ui-architect

Airbnb SwiftUI Best Practices

Opinionated, strict architectural enforcement for SwiftUI iOS apps. Contains 43 rules across 8 categories, derived from Airbnb Engineering, Apple WWDC sessions, Clean Architecture patterns, and Advanced iOS App Architecture (raywenderlich). This skill mandates a single, non-negotiable architecture stack.

Mandated Architecture Stack

┌─────────────────────────────────────────────────────┐
│  Presentation Layer (SwiftUI Views)                 │
│  ├── Views: @Equatable, decomposed, minimal bodies  │
│  ├── ViewModels: @Observable classes via @State      │
│  └── Coordinators: NavigationStack + enum routes     │
├─────────────────────────────────────────────────────┤
│  Domain Layer (Pure Swift)                          │
│  ├── Use Cases / Interactors (stateless, protocol)  │
│  ├── Domain Models (value types, Equatable)         │
│  └── Repository Protocols (abstractions only)       │
├─────────────────────────────────────────────────────┤
│  Data Layer (Implementation)                        │
│  ├── Repository Implementations                     │
│  ├── Network Services                               │
│  └── Persistence (SwiftData / CoreData)             │
└─────────────────────────────────────────────────────┘
Dependency Rule: Arrows point inward only. Views -> Domain <- Data. Domain has zero framework imports.

When to Apply

Reference these guidelines when:
  • Writing any new SwiftUI view, ViewModel, or coordinator
  • Creating or modifying navigation flows
  • Adding data fetching, state management, or dependency injection
  • Reviewing code for architecture compliance or performance
  • Refactoring existing SwiftUI code toward this architecture

Non-Negotiable Constraints (iOS 17+)

  • @Observable
    everywhere,
    ObservableObject
    /
    @Published
    never
  • NavigationStack
    + coordinator pattern,
    NavigationLink(destination:)
    never
  • @Equatable
    macro on every view,
    AnyView
    never
  • Domain layer has zero SwiftUI/UIKit imports
  • Views never access repositories directly

Rule Categories by Priority

PriorityCategoryImpactPrefixRules
1View Identity & DiffingCRITICAL
diff-
6
2State ArchitectureCRITICAL
state-
7
3View CompositionHIGH
view-
6
4Navigation & CoordinationHIGH
nav-
5
5Layer ArchitectureHIGH
layer-
6
6Dependency InjectionMEDIUM-HIGH
di-
4
7List & Collection PerformanceMEDIUM
list-
4
8Async & Data FlowMEDIUM
data-
5

Quick Reference

1. View Identity & Diffing (CRITICAL)

  • diff-equatable-views
    - Apply @Equatable macro to every SwiftUI view
  • diff-closure-skip
    - Use @SkipEquatable for closure/handler properties
  • diff-reference-types
    - Never store reference types without Equatable conformance
  • diff-identity-stability
    - Use stable O(1) identifiers in ForEach
  • diff-avoid-anyview
    - Never use AnyView — use @ViewBuilder or generics
  • diff-printchanges-debug
    - Use _printChanges() to diagnose unnecessary re-renders

2. State Architecture (CRITICAL)

  • state-observable-class
    - Use @Observable classes for all ViewModels
  • state-ownership
    - @State for owned data, plain property for injected data
  • state-single-source
    - One source of truth per piece of state
  • state-scoped-observation
    - Leverage @Observable property-level tracking
  • state-binding-minimal
    - Pass @Binding only for two-way data flow
  • state-environment-global
    - Use @Environment for app-wide shared dependencies
  • state-no-published
    - Never use @Published or ObservableObject

3. View Composition (HIGH)

  • view-body-complexity
    - Maximum 10 nodes in view body
  • view-extract-subviews
    - Extract computed properties/helpers into separate View structs
  • view-no-logic-in-body
    - Zero business logic in body
  • view-minimal-dependencies
    - Pass only needed properties, not entire models
  • view-viewbuilder-composition
    - Use @ViewBuilder for conditional composition
  • view-no-init-sideeffects
    - Never perform work in View init

4. Navigation & Coordination (HIGH)

  • nav-coordinator-pattern
    - Every feature has a coordinator owning NavigationStack
  • nav-routes-enum
    - Define all routes as a Hashable enum
  • nav-deeplink-support
    - Coordinators must support URL-based deep linking
  • nav-modal-sheets
    - Present modals via coordinator, not inline
  • nav-no-navigationlink
    - Never use NavigationLink(destination:) — use navigationDestination(for:)

5. Layer Architecture (HIGH)

  • layer-dependency-rule
    - Domain layer has zero framework imports
  • layer-usecase-protocol
    - Every use case is a protocol with a single execute method
  • layer-repository-protocol
    - Repository protocols in Domain, implementations in Data
  • layer-model-value-types
    - Domain models are structs, never classes
  • layer-no-view-repository
    - Views never access repositories directly
  • layer-viewmodel-boundary
    - ViewModels expose display-ready state only

6. Dependency Injection (MEDIUM-HIGH)

  • di-environment-injection
    - Inject via @Environment with custom EnvironmentKey
  • di-protocol-abstraction
    - All injected dependencies are protocol types
  • di-container-composition
    - Compose dependency container at app root
  • di-mock-testing
    - Every protocol dependency has a mock for testing

7. List & Collection Performance (MEDIUM)

  • list-constant-viewcount
    - ForEach must produce constant view count per element
  • list-filter-in-model
    - Filter/sort in ViewModel, never inside ForEach
  • list-lazy-stacks
    - Use LazyVStack/LazyHStack for unbounded content
  • list-id-keypath
    - Provide explicit id keyPath — never rely on implicit identity

8. Async & Data Flow (MEDIUM)

  • data-task-modifier
    - Use .task {} for async data loading
  • data-async-init
    - Never perform async work in init
  • data-error-loadable
    - Model loading states as enum, not booleans
  • data-combine-avoid
    - Prefer async/await over Combine for new code
  • data-cancellation
    - Use .task automatic cancellation — never manage Tasks manually

How to Use

Read individual reference files for detailed explanations and code examples:
  • Section definitions - Category structure and impact levels
  • Rule template - Template for adding new rules

Reference Files

FileDescription
references/_sections.mdCategory definitions and ordering
assets/templates/_template.mdTemplate for new rules
metadata.jsonVersion and reference information