Get the FREE Ultimate OpenClaw Setup Guide →

swiftui-component

npx machina-cli add skill jeremieb/swift-unit-test-instructions/swiftui-component --openclaw
Files (1)
SKILL.md
6.5 KB

SwiftUI Component Creator

Instructions

Step 1: Clarify Requirements

Ask if not already provided:

  • What does the view display? (list, detail, form, modal, etc.)
  • What user interactions does it support? (tap, swipe, input, navigation)
  • What data does it need? (local state, fetched from API, passed in)
  • Any navigation behavior? (NavigationLink, sheet, fullScreenCover)
  • Min iOS version? (affects @Observable vs ObservableObject)

Step 2: Choose the State Pattern

iOS TargetPattern
iOS 17+@Observable macro on ViewModel, @State private var viewModel in View
iOS 16 and belowObservableObject + @Published, @StateObject in View

Step 3: Generate the Files

Always generate two files: one View, one ViewModel.

View template (iOS 17+ / @Observable):

// Features/[Name]/[Name]View.swift
import SwiftUI

struct [Name]View: View {
    @State private var viewModel: [Name]ViewModel

    init(viewModel: [Name]ViewModel = [Name]ViewModel()) {
        _viewModel = State(initialValue: viewModel)
    }

    var body: some View {
        content
            .task { await viewModel.onAppear() }
            .alert("Error", isPresented: .constant(viewModel.error != nil)) {
                Button("OK") { viewModel.dismissError() }
            } message: {
                Text(viewModel.error?.localizedDescription ?? "")
            }
    }

    @ViewBuilder
    private var content: some View {
        if viewModel.isLoading {
            ProgressView()
        } else {
            mainContent
        }
    }

    private var mainContent: some View {
        // Primary UI here
        Text(viewModel.title)
            .accessibilityLabel(viewModel.title)
    }
}

#Preview {
    [Name]View(viewModel: [Name]ViewModel())
}

View template (iOS 16 / ObservableObject):

// Features/[Name]/[Name]View.swift
import SwiftUI

struct [Name]View: View {
    @StateObject private var viewModel: [Name]ViewModel

    init(viewModel: [Name]ViewModel = [Name]ViewModel()) {
        _viewModel = StateObject(wrappedValue: viewModel)
    }

    var body: some View {
        // same structure as above
    }
}

ViewModel template (iOS 17+ / @Observable):

// Features/[Name]/[Name]ViewModel.swift
import Foundation
import Observation

@MainActor
@Observable
final class [Name]ViewModel {
    // MARK: - Output State
    private(set) var isLoading = false
    private(set) var title: String = ""
    private(set) var error: Error?

    // MARK: - Dependencies
    private let service: [Service]Protocol

    init(service: [Service]Protocol = [Service]()) {
        self.service = service
    }

    // MARK: - Actions
    func onAppear() async {
        isLoading = true
        defer { isLoading = false }
        do {
            title = try await service.fetchTitle()
        } catch {
            self.error = error
        }
    }

    func dismissError() {
        error = nil
    }
}

ViewModel template (iOS 16 / ObservableObject):

// Features/[Name]/[Name]ViewModel.swift
import Foundation

@MainActor
final class [Name]ViewModel: ObservableObject {
    @Published private(set) var isLoading = false
    @Published private(set) var title: String = ""
    @Published private(set) var error: Error?

    private let service: [Service]Protocol

    init(service: [Service]Protocol = [Service]()) {
        self.service = service
    }

    func onAppear() async {
        isLoading = true
        defer { isLoading = false }
        do {
            title = try await service.fetchTitle()
        } catch {
            self.error = error
        }
    }

    func dismissError() {
        error = nil
    }
}

Step 4: Quality Checklist

Before finalizing, verify:

  • View has zero business logic — only layout and presentation
  • ViewModel has no import SwiftUI (only Foundation, Observation)
  • All external dependencies are injected via init
  • #Preview compiles with default / mock data
  • Loading state is handled
  • Error state is handled (alert, inline message, or empty state)
  • Interactive elements have .accessibilityLabel where needed
  • Navigation is handled via ViewModel output flags, not inline logic

Step 5: Sub-View Extraction

If the view body grows complex (>50 lines), extract sub-views:

// Inline private sub-view (same file — preferred for small extractions)
private extension [Name]View {
    var headerSection: some View {
        VStack(alignment: .leading) {
            // ...
        }
    }
}

For reusable components, create a separate file in Core/Components/.

Examples

Example 1: List screen

User says: "Create a SwiftUI product list screen that fetches products and shows a loading spinner"

Files generated:

  • ProductListView.swift — list with .task, loading overlay, error alert
  • ProductListViewModel.swift — fetches from ProductRepositoryProtocol, manages loading/error state

Example 2: Detail screen

User says: "Build a profile detail screen showing user name, avatar, bio, and a logout button"

Files generated:

  • ProfileView.swift — avatar (AsyncImage), name, bio, logout button
  • ProfileViewModel.swiftonLogout() action, user state, logout confirmation flag

Example 3: Form / input screen

User says: "Create a settings screen with a toggle for notifications and a text field for display name"

Files generated:

  • SettingsView.swift — Form with Toggle and TextField bound to ViewModel
  • SettingsViewModel.swiftnotificationsEnabled, displayName, save() async action

Troubleshooting

Preview crashes: ViewModel has a real dependency with side effects. Add a static var preview: [Name]ViewModel with a mock service, or make the default init safe for previews.

View body is growing too large: Extract into private @ViewBuilder computed properties or private extension sub-views. If a sub-view needs significant logic, give it its own ViewModel.

State not updating in view: For @Observable, ensure @State is used in the View (not @StateObject). For ObservableObject, ensure @Published is on the property.

Source

git clone https://github.com/jeremieb/swift-unit-test-instructions/blob/main/skills/swiftui-component/SKILL.mdView on GitHub

Overview

Creates SwiftUI views and screens that follow MVVM with thin view layers and a dedicated ViewModel. Generated templates include previews, accessibility support, and built-in loading and error states. It adapts to iOS targets (iOS 17+ using @Observable or iOS 16 with ObservableObject).

How This Skill Works

The tool clarifies requirements, selects a state pattern based on the iOS target, and generates two files: a View and a ViewModel with appropriate scaffolding. It provides templates for both iOS 17+ (@Observable) and iOS 16 (ObservableObject), including onAppear handling, loading indicators, and error presentation for a robust, accessible UI.

When to Use It

  • User asks to create a SwiftUI view
  • User requests to build a screen
  • User wants to add a new view
  • User asks to create a component or build the UI for a screen
  • User requests adding a SwiftUI screen or making a new SwiftUI page

Quick Start

  1. Step 1: Clarify Requirements
  2. Step 2: Choose State Pattern based on iOS target (iOS 17+ vs iOS 16)
  3. Step 3: Generate the two files (View and ViewModel) using the templates

Best Practices

  • Clarify view content, interactions, and data needs before generating templates
  • Choose the state pattern based on the target iOS version to ensure compatibility
  • Keep the ViewModel thin; extract business logic away from the View
  • Leverage previews and accessibility labels in the templates
  • Implement loading and error handling consistently to improve UX

Example Use Cases

  • Product list screen that shows a loading indicator while fetching items and presents an error alert on failure
  • Detail view that fetches and displays a title from a service with a loading state
  • Login form with a loading state and error feedback via the ViewModel
  • Settings screen with a title sourced from a ViewModel and accessible controls
  • Modal sheet presenting a detail view using MVVM and a simple onAppear data fetch

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers