Agents swiftui-architecture
Architecture patterns for SwiftUI applications including MVVM, navigation, dependency injection, and the @Observable macro
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/plugins/frontend/swiftui-dev/skills/swiftui-architecture" ~/.claude/skills/arustydev-agents-swiftui-architecture && rm -rf "$T"
manifest:
content/plugins/frontend/swiftui-dev/skills/swiftui-architecture/SKILL.mdsource content
SwiftUI Architecture Patterns
Purpose
Architecture patterns for SwiftUI applications including MVVM, navigation, dependency injection, and the @Observable macro.
Patterns
MVVM with @Observable
import SwiftUI // ViewModel using @Observable macro (iOS 17+/macOS 14+) @Observable final class ContentViewModel { var items: [Item] = [] var isLoading = false var errorMessage: String? private let repository: ItemRepository init(repository: ItemRepository = .live) { self.repository = repository } func loadItems() async { isLoading = true defer { isLoading = false } do { items = try await repository.fetchAll() } catch { errorMessage = error.localizedDescription } } } // View struct ContentView: View { @State private var viewModel = ContentViewModel() var body: some View { List(viewModel.items) { item in ItemRow(item: item) } .overlay { if viewModel.isLoading { ProgressView() } } .task { await viewModel.loadItems() } } }
Dependency Injection
// Protocol-based dependency protocol ItemRepository: Sendable { func fetchAll() async throws -> [Item] func save(_ item: Item) async throws } // Live implementation struct LiveItemRepository: ItemRepository { func fetchAll() async throws -> [Item] { // Real implementation } func save(_ item: Item) async throws { // Real implementation } } // Mock for testing struct MockItemRepository: ItemRepository { var items: [Item] = [] var shouldFail = false func fetchAll() async throws -> [Item] { if shouldFail { throw TestError.fetchFailed } return items } func save(_ item: Item) async throws { // Mock save } } // Static factory pattern extension ItemRepository where Self == LiveItemRepository { static var live: LiveItemRepository { LiveItemRepository() } } extension ItemRepository where Self == MockItemRepository { static var mock: MockItemRepository { MockItemRepository() } }
Navigation with NavigationStack
// Type-safe navigation paths enum NavigationDestination: Hashable { case detail(Item) case settings case profile(userId: String) } struct RootView: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { ContentView(path: $path) .navigationDestination(for: NavigationDestination.self) { destination in switch destination { case .detail(let item): ItemDetailView(item: item) case .settings: SettingsView() case .profile(let userId): ProfileView(userId: userId) } } } } }
Environment-based Dependencies
// Environment key for repository private struct ItemRepositoryKey: EnvironmentKey { static let defaultValue: any ItemRepository = LiveItemRepository() } extension EnvironmentValues { var itemRepository: any ItemRepository { get { self[ItemRepositoryKey.self] } set { self[ItemRepositoryKey.self] = newValue } } } // Usage in view struct ItemListView: View { @Environment(\.itemRepository) private var repository @State private var items: [Item] = [] var body: some View { List(items) { item in Text(item.name) } .task { items = try? await repository.fetchAll() ?? [] } } } // Preview with mock #Preview { ItemListView() .environment(\.itemRepository, MockItemRepository(items: .preview)) }
File Organization
Feature-Based Structure (Recommended)
MyApp/ ├── App/ │ ├── MyApp.swift # @main entry point │ ├── AppDelegate.swift # UIKit lifecycle (if needed) │ └── AppState.swift # Global @Observable state │ ├── Features/ # Feature modules │ ├── Home/ │ │ ├── Views/ │ │ │ ├── HomeView.swift │ │ │ └── HomeCard.swift │ │ ├── ViewModels/ │ │ │ └── HomeViewModel.swift │ │ └── Models/ │ │ └── HomeItem.swift │ │ │ ├── Profile/ │ │ ├── Views/ │ │ ├── ViewModels/ │ │ └── Models/ │ │ │ └── Settings/ │ └── ... │ ├── Core/ # Shared infrastructure │ ├── Repositories/ │ │ └── ItemRepository.swift │ ├── Services/ │ │ ├── Network/ │ │ │ ├── APIClient.swift │ │ │ ├── Endpoints.swift │ │ │ └── NetworkError.swift │ │ └── Persistence/ │ │ ├── DatabaseManager.swift │ │ └── KeychainService.swift │ └── Extensions/ │ ├── View+Extensions.swift │ └── Date+Extensions.swift │ ├── Shared/ # Reusable UI components │ ├── Components/ │ │ ├── LoadingView.swift │ │ ├── ErrorView.swift │ │ └── AsyncButton.swift │ ├── Modifiers/ │ │ ├── CardStyle.swift │ │ └── ShimmerEffect.swift │ └── Styles/ │ └── ButtonStyles.swift │ ├── Utilities/ # Helpers and constants │ ├── Constants.swift │ ├── Logger.swift │ └── Formatters.swift │ ├── Resources/ │ ├── Assets.xcassets # Images and colors │ ├── Localizable.xcstrings # Localized strings (Xcode 15+) │ └── Fonts/ # Custom fonts │ └── CustomFont.ttf │ └── Tests/ ├── UnitTests/ │ ├── ViewModels/ │ │ └── HomeViewModelTests.swift │ └── Services/ │ └── APIClientTests.swift └── UITests/ ├── HomeUITests.swift └── Helpers/ └── XCUIApplication+Extensions.swift
Swift Package Structure
MyAppPackage/ ├── Package.swift ├── Sources/ │ ├── MyApp/ # Main app target │ │ └── MyApp.swift │ ├── Features/ # Feature library │ │ ├── Home/ │ │ └── Profile/ │ ├── Core/ # Core library │ │ ├── Networking/ │ │ └── Persistence/ │ └── SharedUI/ # UI components library │ ├── Components/ │ └── Modifiers/ └── Tests/ ├── CoreTests/ └── FeaturesTests/
Best Practices
- Use @Observable for ViewModels - Simpler than ObservableObject
- Inject dependencies via init - Enables testing
- Use NavigationStack for navigation - Type-safe, deep-linkable
- Keep views small - Extract subviews early
- Use task modifier for async - Proper lifecycle management
Related Skills
- swiftui-data-flow: State management details
- swiftui-components: UI elements and layouts
- swiftui-gestures: User interactions
- swiftui-testing: Testing these patterns
- swift-concurrency: Async/await patterns