name: viewmodel description: Creates ViewModels with state management. Use when creating ViewModels, implementing ViewState pattern, or adding state management for features. Delegates to /usecase for domain use cases and to /feature for Container/Feature wiring.
Skill: ViewModel
Guide for creating ViewModels that manage state and coordinate between Views and UseCases.
Scope & Boundaries
This skill owns only Sources/Presentation/{Screen}/ViewModels/ and its tests.
| Need | Delegate to |
|---|---|
| Domain UseCases | /usecase skill |
| Container / Feature wiring | /feature skill |
| Navigator / Tracker | /navigator skill |
Workflow
Step 1 — Identify ViewModel Type
| Type | When | Reference |
|---|---|---|
| Stateless | Navigation + tracking only, no observable state | references/stateless.md |
| Stateful Detail | Single item load + optional refresh | references/stateful-detail.md |
| Stateful List | List load + empty/error + optional refresh | references/stateful-list.md |
| Debounced Search | Search with debounce + recent searches (extends list) | references/debounced-search.md |
| Stateful Filter | Mutable filter with computed properties | references/stateful-filter.md |
Step 2 — Ensure UseCases Exist
Before creating the ViewModel, verify required UseCases exist in Sources/Domain/UseCases/.
- UseCases found? → Go to Step 3
- No UseCases found? → Invoke the
/usecaseskill first. Return here after completion.
Step 3 — Implement ViewModel
Read the appropriate reference from Step 1 and implement. Each reference includes: Contract, ViewState (if applicable), ViewModel, View integration snippet, Container factory snippet, Test structure, Mock, and Stub.
- Contract + ViewState + ViewModel in
Sources/Presentation/{Screen}/ViewModels/ - Mock in
Tests/Shared/Mocks/ - Tests in
Tests/Unit/Presentation/{Screen}/ViewModels/ - Run tests
Core Conventions
Class Rules
@Observableonly whenprivate(set) varstate exists; stateless ViewModels are plainfinal classfinal class, internal visibility, no explicit@MainActor(project default isolation)- Protocol =
{Screen}ViewModelContract: AnyObject
Method Naming
All protocol methods describe the UI event, using the did prefix:
| UI Event | Protocol Method |
|---|---|
View appears (.onFirstAppear {}) |
didAppear() |
| Tap retry button | didTapOnRetryButton() |
Pull to refresh (.refreshable {}) |
didPullToRefresh() |
| Tap load more button | didTapOnLoadMoreButton() |
| Item selection | didSelect(_:) |
| Tap on button | didTapOn{ButtonName}() |
Behavior Rules
didAppear(): Called once via.onFirstAppear— single execution guaranteed by the ViewdidTapOnRetryButton(): Always callsload()unconditionallydidPullToRefresh(): Always calls the refresh use case, resets paginationdidTapOnLoadMoreButton(): Only loads if there is a next page and not already loading more- Public methods (
didAppear,didTapOnRetryButton) call privateload()— encapsulate loading logic
ViewState == Operator
All ViewState enums implement == for testability (enables direct state comparison in tests). Error cases compare via localizedDescription. See reference files for templates.
File Structure
Features/{Feature}/
├── Sources/Presentation/{Screen}/
│ └── ViewModels/
│ ├── {Screen}ViewModelContract.swift
│ ├── {Screen}ViewState.swift # Only for stateful ViewModels
│ └── {Screen}ViewModel.swift
└── Tests/
├── Unit/Presentation/{Screen}/
│ └── ViewModels/
│ └── {Screen}ViewModelTests.swift
└── Shared/Mocks/
└── {Screen}ViewModelMock.swift
Visibility Summary
| Component | Visibility | Location |
|---|---|---|
| ViewModelContract | internal | Sources/Presentation/{Screen}/ViewModels/ |
| ViewState | internal | Sources/Presentation/{Screen}/ViewModels/ |
| ViewModel | internal | Sources/Presentation/{Screen}/ViewModels/ |
| Mock | internal | Tests/Shared/Mocks/ |
Checklist
- Create ViewModelContract (always
AnyObject) - Create ViewState enum with
==operator (stateful only) - Create ViewModel (
@Observablefor stateful, plainfinal classfor stateless) - Inject UseCases via protocol (contract)
- Inject NavigatorContract for navigation
- Inject TrackerContract for tracking
- Implement
didAppear()/didTapOnRetryButton()as public,load()as private - Add tracking calls in
didAppear(),didSelect(),didTapOn...()methods - Guard observable properties with
oldValuecheck indidSet(search only) - Create Mock in
Tests/Shared/Mocks/ - Create tests for initial state, success, error, and call verification
- Run tests

