Get the FREE Ultimate OpenClaw Setup Guide →

ios-app-builder

npx machina-cli add skill mouchegmouradian/claude-code-skills/ios-app-builder --openclaw
Files (1)
SKILL.md
9.6 KB

iOS Development

Build iOS applications following Apple's official architecture guidance, modern Swift concurrency patterns, and SwiftUI best practices.

Quick Reference

TaskReference File
Architecture layers (UI, Domain, Data)architecture.md
SwiftUI patterns & navigationswiftui-patterns.md
Swift Actors & concurrencyactors.md
Project & build configurationproject-setup.md
Project structure & modulesmodularization.md
Testing approachtesting.md

Workflow Decision Tree

Creating a new project? → Read project-setup.md for SPM and Xcode setup → Read modularization.md for module structure → Use templates in assets/templates/

Adding a new feature? → Create feature module following modularization.md → Follow patterns in architecture.md

Building UI screens? → Read swiftui-patterns.md → Create Route + Screen + ViewModel

Setting up data layer? → Read data layer section in architecture.md → Create Repository protocol + actor implementation + @ModelActor

Working with concurrency? → Read actors.md for actors, Sendable, async/await → Follow the actor decision tree

Writing tests? → Read testing.md → Use Swift Testing framework with protocol-based test doubles

Core Principles

  1. Offline-first: Local persistence (SwiftData) is source of truth, sync with remote
  2. Unidirectional data flow: Events flow down, data flows up
  3. Reactive streams: Use AsyncSequence/AsyncStream for all data exposure
  4. Modular by feature: Each feature is self-contained via Swift Package modules
  5. Testable by design: Use protocols and test doubles, no mocking libraries
  6. Actor-isolated concurrency: Use actors for shared mutable state, @MainActor for UI

Architecture Layers

┌─────────────────────────────────────────┐
│              UI Layer                    │
│  (SwiftUI Views + @Observable VMs)      │
├─────────────────────────────────────────┤
│           Domain Layer                   │
│  (Use Cases - optional, for reuse)       │
├─────────────────────────────────────────┤
│            Data Layer                    │
│  (Repositories + DataSources)            │
└─────────────────────────────────────────┘

Module Types

App/                        # App target - entry point, navigation, @main
Features/
  ├── FeatureName/
  │   ├── Sources/          # Route, Screen, ViewModel
  │   └── Tests/            # Feature tests
Core/
  ├── Model/                # Domain models (pure Swift structs)
  ├── Data/                 # Repositories (protocols + actor implementations)
  ├── Database/             # SwiftData models, @ModelActor
  ├── Network/              # URLSession, API models, DTOs
  ├── Common/               # Shared utilities, extensions
  ├── UI/                   # Reusable SwiftUI components
  ├── DesignSystem/         # Theme, colors, typography, SF Symbols
  └── Testing/              # Test utilities, test doubles

Creating a New Feature

  1. Create feature module in Features/MyFeature/
  2. Add target to Package.swift with dependencies on Core modules
  3. Create these files:
    • MyFeatureRoute.swift - Owns ViewModel (@State), handles navigation
    • MyFeatureScreen.swift - Receives ViewModel (let), renders UI
    • MyFeatureViewModel.swift - @MainActor @Observable with individual properties

Standard File Patterns

ViewModel Pattern

Expose individual properties — SwiftUI's @Observable tracks each property accessed per view, enabling granular re-renders. Avoid wrapping state in a single enum (that forces full re-renders).

@MainActor
@Observable
final class MyFeatureViewModel {
    private(set) var items: [Item] = []
    private(set) var isLoading = false
    var errorMessage: String?

    private let repository: MyRepository

    init(repository: MyRepository) {
        self.repository = repository
    }

    func onAppear() async {
        isLoading = true
        defer { isLoading = false }
        do {
            items = try await repository.getItems()
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    func deleteItem(id: String) async {
        do {
            try await repository.deleteItem(id: id)
            items.removeAll { $0.id == id }
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    func refresh() async {
        await onAppear()
    }
}

Screen Pattern (Route/Screen Separation)

  • Route creates and owns the ViewModel with @State
  • Screen receives the ViewModel as let — no wrapper needed, SwiftUI auto-tracks @Observable
  • For two-way bindings (TextField, Toggle), use @Bindable locally in the body
// Route: owns ViewModel via @State, connects navigation
struct MyFeatureRoute: View {
    @State private var viewModel: MyFeatureViewModel

    init(repository: MyRepository) {
        viewModel = MyFeatureViewModel(repository: repository)
    }

    var body: some View {
        MyFeatureScreen(viewModel: viewModel)
            .task { await viewModel.onAppear() }
            .refreshable { await viewModel.refresh() }
            .navigationTitle("My Feature")
    }
}

// Screen: receives ViewModel, reads properties directly
struct MyFeatureScreen: View {
    let viewModel: MyFeatureViewModel

    var body: some View {
        List(viewModel.items) { item in
            Text(item.name)
                .swipeActions {
                    Button("Delete", role: .destructive) {
                        Task { await viewModel.deleteItem(id: item.id) }
                    }
                }
        }
        .overlay {
            if viewModel.isLoading && viewModel.items.isEmpty {
                ProgressView()
            }
        }
        .overlay {
            if let error = viewModel.errorMessage, viewModel.items.isEmpty {
                ContentUnavailableView(
                    "Error",
                    systemImage: "exclamationmark.triangle",
                    description: Text(error)
                )
            }
        }
    }
}

When to use a state enum instead: Use enum UiState only for truly mutually exclusive states like onboarding flows (step1 | step2 | step3) or auth state (loggedIn | loggedOut), where you cannot show content and loading simultaneously.

Property Wrapper Decision (WWDC 2023)

View roleWrapperExample
Creates the ViewModel@State@State private var viewModel = MyVM()
Receives from parentlet (none)let viewModel: MyVM
Needs $ bindings@Bindable@Bindable var viewModel = viewModel
Shared app-wide@Environment@Environment(AppState.self) var appState

Repository Pattern

// Protocol: defines contract, Sendable for cross-isolation use
protocol MyRepository: Sendable {
    func getItems() async throws -> [MyModel]
    func observeItems() -> AsyncStream<[MyModel]>
    func updateItem(_ item: MyModel) async throws
    func sync() async throws
}

// Implementation: actor for thread-safe shared state
actor OfflineFirstMyRepository: MyRepository {
    private let persistence: MyPersistenceActor
    private let network: NetworkClient

    init(persistence: MyPersistenceActor, network: NetworkClient) {
        self.persistence = persistence
        self.network = network
    }

    func getItems() async throws -> [MyModel] {
        try await persistence.fetchAll()
    }

    func observeItems() -> AsyncStream<[MyModel]> {
        persistence.observeAll()
    }

    func updateItem(_ item: MyModel) async throws {
        try await persistence.upsert(item)
    }

    func sync() async throws {
        let remoteItems = try await network.fetchItems()
        for item in remoteItems {
            try await persistence.upsert(item.toDomainModel())
        }
    }
}

Key Dependencies

Swift 6+ (strict concurrency)
iOS 17+ (required for @Observable, SwiftData, #Preview)
SwiftData (persistence)
Swift Testing (unit tests)
XCTest (UI tests)

Build Configuration

Use Swift Package Manager for multi-module projects:

  • Local package inside Xcode workspace
  • Each feature and core module is a separate target
  • Strict concurrency checking enabled (-strict-concurrency=complete)
  • Swift 6 language mode

See project-setup.md for complete configuration.

Source

git clone https://github.com/mouchegmouradian/claude-code-skills/blob/main/skills/ios-app-builder/SKILL.mdView on GitHub

Overview

Build production-quality iOS apps following Apple's architecture guidance and modern Swift patterns. It emphasizes SwiftUI, MVVM, Swift Concurrency (actors, async/await), SwiftData, and a modular architecture across App, Features, and Core layers. It triggers on requests to scaffold projects, screens, ViewModels, repositories, feature modules, actors, or architecture patterns.

How This Skill Works

Workflows are driven by a decision-tree referencing project-setup.md, modularization.md, and templates in assets/templates to scaffold code. The architecture is layered (UI, Domain, Data) and features are modularized as Swift Package modules, with data layers built from repositories, actors, and @ModelActor using SwiftData. Concurrency usage is guided by actors and async/await patterns.

When to Use It

  • Starting a new iOS app using SwiftUI, MVVM, and modern concurrency
  • Adding a new feature or feature module to an existing app
  • Creating UI screens with a dedicated Route, Screen, and ViewModel
  • Setting up the data layer with repositories, SwiftData models, and @ModelActor
  • Working on multi-module projects and applying Swift Concurrency patterns

Quick Start

  1. Step 1: Read project-setup.md for SPM/Xcode setup and modularization.md for module structure
  2. Step 2: Create a new feature module under Features/MyFeature and add a target in Package.swift
  3. Step 3: Create Route + Screen + ViewModel and implement their integration with the data layer and core modules via templates in assets/templates/

Best Practices

  • Modularize by feature: keep each feature in its own Swift Package module
  • Make SwiftData the source of truth for offline-first persistence
  • Enforce unidirectional data flow: events down, data up
  • Use actors for shared mutable state and @MainActor for UI-bound code
  • Design for testability with protocols and test doubles rather than mocks

Example Use Cases

  • Build a modular iOS app with Features/MyFeature and Core modules
  • Implement Route + Screen + ViewModel for a new screen
  • Add a Repository protocol and actor-based data source using SwiftData
  • Refactor an app to use MVVM, SwiftUI patterns, and modularization
  • Migrate a monolithic app into a feature-driven architecture with Swift Package modules

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers