swift-mvvm

Use when writing or refactoring Swift (SwiftUI/UIKit/AppKit) code to follow MVVM with a small, testable ViewModel. Triggers on: MVVM, ViewModel, ObservableObject, @Observable, Observation, AppKit, NSViewController, UIKit, state management, dependency injection, protocol adapters, refactor view logic, massive view model, testable, async/await, Combine, Swift Testing, #expect.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "swift-mvvm" with this command: npx skills add tobitech/swift-mvvm/tobitech-swift-mvvm-swift-mvvm

Swift MVVM Skill

Help the agent produce better MVVM code for Swift projects.

Core stance

  • Keep the ViewModel UI-framework agnostic. ViewModels should not import SwiftUI, UIKit, or AppKit.
  • The ViewModel should be mostly (1) state, (2) intent methods, and (3) dependency coordination.
  • Push work into smaller, testable units (pure structs/functions, use cases, controllers, repositories, mappers, formatters).
  • Use dependency injection with protocols so everything is mockable.
  • Extensions are encouraged for organization (especially protocol conformances).

When to use

Use this skill when the user asks to:

  • Add or refactor features in SwiftUI/UIKit/AppKit and keep architecture clean.
  • Create or improve a ViewModel (state, intents, effects) and move logic out of Views/ViewControllers.
  • Fix state-management issues (wrong wrappers, threading warnings, unstable bindings).
  • Improve testability (protocol-based dependencies, mocks/fakes, deterministic state updates).
  • Reduce a “massive ViewModel” by splitting concerns.

First check (before writing code)

  1. Identify UI tech: SwiftUI, UIKit, or AppKit.
  2. Identify deployment target:
    • If iOS 17+/macOS 14+ is available, prefer Observation (@Observable).
    • Otherwise prefer Combine-based ObservableObject + @Published.
  3. Match existing project style (naming, folders, DI approach, networking layer).

MVVM responsibilities

View layer

SwiftUI View / UIKit UIViewController / AppKit NSViewController:

  • Declares layout and binds to state.
  • Sends user intents (tap, selection, text changes) to the ViewModel.
  • Owns UI-only concerns (navigation, presenting alerts, AppKit panels, first responder, etc.).

ViewModel

  • Owns screen state (loading/data/error, derived UI values).
  • Coordinates effects through injected dependencies.
  • Exposes intent methods (onAppear(), refresh(), didTap…) rather than views calling random internals.

Domain / Services

  • Encapsulate fetching, caching, persistence, decoding, validation.
  • Must be independent of UI frameworks.

Hard rule: No UI framework imports in ViewModels

ViewModels should not import:

  • SwiftUI
  • UIKit
  • AppKit

If a ViewModel needs a platform behavior, define a tiny protocol in a non-UI module (Foundation-only), and provide a platform implementation in the View layer (or platform adapter module).

Example:

// In a Foundation-only target / file.
protocol FileRevealing {
  func reveal(_ url: URL)
}

// In AppKit layer.
import AppKit

struct WorkspaceFileRevealer: FileRevealing {
  func reveal(_ url: URL) {
    NSWorkspace.shared.activateFileViewerSelecting([url])
  }
}

Any “AppKit-y” or “UIKit-y” behavior inside the ViewModel is a **smell**.

## Preferred ViewModel shapes

Pick one based on scope.

### Pattern A: Simple screen

- `State` struct (nested)
- intent methods
- async `load()` with cancellation

### Pattern B: Complex screen

- `State` struct (nested)
- `Action` enum + `send(_:)`
- reducer-like switch for state transitions
- side effects delegated to injected units

## Keep state structured (avoid a ViewModel with 30 vars)

Prefer:

```swift
struct State: Equatable {
  var view = ViewState()
  var content = ContentState()
  var alerts = AlertsState()

  struct ViewState: Equatable {
    var isLoading = false
    var title = ""
  }

  struct ContentState: Equatable {
    var rows: [Row] = []
    var emptyMessage: String? = nil
  }

  struct AlertsState: Equatable {
    var error: ErrorState? = nil
  }
}

Refactoring a massive ViewModel (priority order)

When reducing a large VM, refactor in this order:

  1. Extract pure logic first
  • Move non-IO computations into pure structs or pure functions.
  • Examples: filtering, sorting, mapping domain models to row models, formatting, validation, state reducers.
  1. Extract side effects into controllers (still testable)
  • Create small “effect” units that do IO and orchestration.
  • Keep them behind protocols and inject into the VM.
  • Examples: LoadExamplesUseCase, ExamplesController, AnalyticsTracking, FileRevealing.
  1. Leave the VM as state + intents
  • VMs forward intents to pure logic/effect units and assign state.

Concurrency & cancellation rules

  • UI state changes should happen on the main actor.
  • Store ongoing tasks and cancel them when a new request starts or when the view disappears.

SwiftUI integration rules

Observation (@Observable)

  • Hold a ViewModel instance using @State (owner) and pass it down.

Combine (ObservableObject)

  • Use @StateObject when the view creates/owns the ViewModel.
  • Use @ObservedObject when the view is given the ViewModel.

Dependency injection rules

  • ViewModel takes dependencies in its initializer.
  • Dependencies are protocols; provide a production implementation and a mock/fake for tests.
  • Prefer injecting small units:
    • Pure logic: RowBuilder, Validator, Reducer
    • Effects: UseCase, Controller, Repository
    • Platform adapters: FileRevealing, URLOpening, etc.

Avoid massive ViewModels

Smells:

  • Imports UI frameworks.
  • Does URLSession/JSON decoding directly.
  • Builds NSAlert/UIAlertController.
  • Knows about NSWorkspace/UIApplication.
  • Formats everything inline with complex logic.
  • Handles navigation, analytics, networking, caching all together.

Refactor moves:

  • Extract a pure RowBuilder or Reducer.
  • Extract a UseCase for business rules.
  • Extract a Repository for IO.
  • Extract a side-effects Controller to orchestrate multiple services.
  • Extract platform adapters (tiny protocols) for UI actions.

Output expectations

When writing/refactoring:

  • Provide code in diff-friendly chunks, grouped by file.
  • Prefer small, composable functions.
  • Use extensions for organization (e.g., protocol conformances, grouping helpers).
  • Name clearly: FooViewModel, FooState, FooUseCase, FooRepository, FooController, FooRowBuilder.
  • Add tests for ViewModel state transitions and extracted pure logic.

Testing guidance

Prefer Swift Testing (import Testing, @Test, #expect, #require) for new tests.

  • Test pure logic units directly (fast, deterministic).
  • Test ViewModel state transitions (success/failure/cancellation) by injecting mocks.

Templates

Copy/paste from:

  • templates/ObservationViewModel.swift
  • templates/CombineViewModel.swift
  • templates/AppKitViewController.swift
  • templates/ProtocolAdapters.swift
  • templates/ServiceUseCaseControllerAndPureLogic.swift
  • templates/ViewModelTests.swift (Swift Testing)

Additional resources (load only if needed)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Awesome Cheatsheets

👩‍💻👨‍💻 Awesome cheatsheets for popular programming languages, frameworks and development tools. They awesome cheatsheets, javascript, backend, bash, chea...

Registry SourceRecently Updated
Coding

Appsmith

Platform to build admin panels, internal tools, and dashboards. Integrates with 25+ databases and an appsmith, typescript, admin-dashboard, admin-panels, app...

Registry SourceRecently Updated
Coding

Apicheck

API请求构造、curl命令生成、Mock数据、API文档、HTTP状态码速查、Headers说明。API request builder, curl generator, mock data, API documentation, HTTP status codes, headers reference. Us...

Registry SourceRecently Updated
Coding

Api Router

Gorilla: Training and Evaluating LLMs for Function Calls (Tool Calls) api-router, python, api, api-documentation, chatgpt, claude-api, gpt-4-api. Use when yo...

Registry SourceRecently Updated