swift-architecture
This Swift Architecture skill guides selection and implementation of design patterns for Apple platform apps built with Swift 6.3, SwiftUI, and UIKit. Use it when choosing between MV with @Observable, MVVM, MVI, TCA, Clean Architecture, Coordinator, or VIPER patterns; evaluating whether a pattern fits a feature's complexity; migrating between patterns; or assessing if an app's current architecture remains appropriate as it evolves.
git clone --depth 1 https://github.com/dpearson2699/swift-ios-skills /tmp/swift-architecture && cp -r /tmp/swift-architecture/skills/swift-architecture ~/.claude/skills/swift-architectureSKILL.md
# Swift Architecture
Select and implement the right architecture pattern for Apple platform apps built with Swift 6.3 and SwiftUI or UIKit.
## Contents
- [Scope Boundary](#scope-boundary)
- [Architecture Selection](#architecture-selection)
- [MV Pattern (Model-View with `@Observable`)](#mv-pattern)
- [MVVM](#mvvm)
- [MVI (Model-View-Intent)](#mvi)
- [TCA (The Composable Architecture)](#tca)
- [Clean Architecture](#clean-architecture)
- [Coordinator Pattern](#coordinator-pattern)
- [VIPER](#viper)
- [Migration Between Patterns](#migration-between-patterns)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
## Scope Boundary
This skill owns architecture-level decisions: pattern selection, module
boundaries, dependency direction, migration/escalation strategy, and structural
test strategy. It does not own SwiftUI state mechanics; route `@State`,
`@Bindable`, `@Environment`, edit-sheet/local state, bindings, view composition,
and `@Observable` MV implementation mechanics to `swiftui-patterns`. Use
`swiftui-navigation` for `NavigationStack`, `NavigationSplitView`,
`NavigationPath`, route models, sheets, tabs, and deep-link URL handling;
`swift-concurrency` for `@MainActor`, default MainActor isolation, `Sendable`,
strict-concurrency diagnostics, and data-race diagnostics; and `swift-testing`
for `@Test`, `#expect`, `#require`, fixtures, parameterized tests, mocks, stubs,
and suite organization.
## Architecture Selection
| Pattern | Best For | Complexity | Testability |
|---------|----------|-----------|-------------|
| **MV** | Small-to-medium SwiftUI apps, rapid iteration | Low | Moderate |
| **MVVM** | Medium apps, teams familiar with reactive patterns | Medium | High |
| **MVI** | Complex state machines, predictable state flow | Medium-High | High |
| **TCA** | Large apps needing composable features, strong testing | High | Very High |
| **Clean Architecture** | Enterprise apps, strict separation of concerns | High | Very High |
| **Coordinator** | Apps with complex navigation flows (UIKit or hybrid) | Medium | High |
| **VIPER** | Legacy UIKit modules already using VIPER boundaries | Very High | High |
**Default recommendation for new SwiftUI apps:** Start with MV (Model-View
with `@Observable`). Escalate to MVVM or TCA only when the feature's complexity
demands it.
Boundary-split answers should use one `swift-architecture` bucket for
pattern/module/dependency/migration/test-strategy decisions. Do not add a
separate architecture-owned "SwiftUI state ownership" bucket; property-wrapper,
local binding, navigation, concurrency-diagnostic, fixture, and parameterized
test mechanics are sibling-skill handoffs.
### Decision Framework
1. **Is the feature a simple CRUD screen?** → MV pattern
2. **Does the screen have complex business logic separate from the view?** → MVVM
3. **Do you need deterministic state transitions and side-effect management?** → MVI or TCA
4. **Is the app large with many independent feature modules?** → TCA or Clean Architecture
5. **Is navigation complex with deep linking and conditional flows?** → Add Coordinator pattern
## MV Pattern
The simplest SwiftUI architecture. The view observes `@Observable` models
directly. No intermediate view model layer.
```swift
import Observation
import SwiftUI
@MainActor
@Observable
final class TripStore {
var trips: [Trip] = []
var isLoading = false
var error: Error?
private let service: TripService
init(service: TripService) {
self.service = service
}
func loadTrips() async {
isLoading = true
defer { isLoading = false }
do {
trips = try await service.fetchTrips()
} catch {
self.error = error
}
}
func deleteTrip(_ trip: Trip) async throws {
try await service.delete(trip)
trips.removeAll { $0.id == trip.id }
}
}
struct TripsView: View {
@State private var store = TripStore(service: .live)
var body: some View {
List(store.trips) { trip in
TripRow(trip: trip)
}
.task { await store.loadTrips() }
}
}
```
**When MV is enough:** Single-screen features, prototype/MVP, small teams,
straightforward data flow.
**When to upgrade:** Business logic grows complex, unit testing the view's
behavior becomes difficult, multiple views need to share and transform the
same state differently.
## MVVM
Separates view logic into a `ViewModel` that the view observes. The view model
transforms model data for display and handles user actions.
```swift
@MainActor
@Observable
final class TripListViewModel {
private(set) var trips: [TripRowItem] = []
private(set) var isLoading = false
var searchText = ""
var filteredTrips: [TripRowItem] {
guard !searchText.isEmpty else { return trips }
return trips.filter { $0.name.localizedStandardContains(searchText) }
}
private let repository: TripRepository
init(repository: TripRepository) {
self.repository = repository
}
func loadTrips() async {
isLoading = true
defer { isLoading = false }
let models = (try? await repository.fetchAll()) ?? []
trips = models.map { TripRowItem(from: $0) }
}
func delete(at offsets: IndexSet) async {
let toDelete = offsets.map { filteredTrips[$0] }
for item in toDelete {
try? await repository.delete(id: item.id)
}
await loadTrips()
}
}
struct TripRowItem: Identifiable {
let id: UUID
let name: String
let dateRange: String
init(from trip: Trip) {
self.id = trip.id
self.name = trip.name
self.dateRange = trip.startDate.formatted(.dateTime.month().day())
+ " – " + trip.endDate.formatted(.dateTime.month().day())
}
}
struct TripListView: View {
@State private var viewModel: TripListViewModel
init(repository: TripRepository) {
_viewMDiscover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.
Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.
Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.
Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, AlarmButton stop and snooze actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.
Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.
Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.
Optimize App Store product pages for search visibility and conversion. Use for App Store Optimization (ASO), keyword research, app name/subtitle/keyword-field strategy, conversion-focused descriptions and promotional text, screenshot captions and ordering, Custom Product Pages with assigned search keywords, In-App Events, Product Page Optimization tests, localized metadata, ratings/review strategy, and in-app review prompt timing with RequestReviewAction or AppStore.requestReview. Also use when routing ASO vs App Store review, privacy/ATT, or StoreKit implementation boundaries.
Prepare for App Store review and prevent rejections. Covers App Store review guidelines, app rejection reasons, PrivacyInfo.xcprivacy privacy manifest requirements, required API reason codes, in-app purchase IAP and StoreKit rules, App Store Guidelines compliance, ATT App Tracking Transparency, EU DMA Digital Markets Act, HIG compliance checklist, app submission preparation, review preparation, metadata requirements, entitlements, widgets, and Live Activities review rules. Use when preparing for App Store submission, fixing rejection reasons, auditing privacy manifests, implementing ATT consent flow, configuring StoreKit IAP, or checking HIG compliance.