Skip to content
cccskills
FreePublished on GitHub

swiftui-patterns

Use when building native iOS with SwiftUI - small composable views, observable state models, atomic components, Swift 6.2+ concurrency, and system styling over hardcoded values

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md6.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

SwiftUI Patterns

Overview

Native iOS (SwiftUI) is chosen when a task needs deep Apple-platform integration or the app is already native (see native-vs-flutter-decision). Same discipline as Flutter: small composed views, state out of the view, reusable atomic components.

Core principle: Views are a function of state. Data and business logic live in an observable model, not in the View.

Rules

  • Small views, composed. Extract subviews as their own View types (not @ViewBuilder funcs) so each has its own body and preview.
  • State ownership is explicit: @State for local ephemeral UI only (and it is always private); app/business state in an @MainActor @Observable model injected via init/@Environment. Views send intents to the model; the model owns the logic and the networking.
  • Atomic components: a shared component library (atoms → molecules → organisms) of reusable views. Reuse before building; grep before adding.
  • System styling over hardcoded values: colors from the asset catalog / Color semantic roles, spacing/typography from a tokens file (see mobile-design-system-foundation) — not scattered magic numbers. Support Dynamic Type and dark mode.
  • #Preview every reusable view in its key states (loading/data/error), and in the matrix: Dynamic Type .accessibility3, dark, traits: .landscapeLeft.
  • Value types (struct) for models and view state; reference types only where identity is needed.
  • Never name a domain type Task — it shadows _Concurrency.Task, which every async/.task {} call site needs; a rename (BoardItem, WorkItem, …) is a five-minute fix versus a confusing compile error later.

Concurrency

  • Follow the target's default actor isolation: a new Xcode 26 project defaults to @MainActor isolation, so a view model that touches UI state should be @MainActor @Observable explicitly rather than relying on inference holding across Swift versions.
  • No GCD (DispatchQueue.main.async, etc.) in new code — async/await, Task { }, Task.sleep(for:).
  • Kick off async work from .task { } on the view, not onAppear + a detached Task.

Worked Example

struct BoardItem: Identifiable, Sendable { let id: Int; let title: String }   // never name a model `Task`
enum LoadState<T> { case loading, empty, data(T), failed(String) }

@MainActor @Observable final class ItemListModel {
    private(set) var state: LoadState<[BoardItem]> = .loading
    private let repo: any BoardItemRepository
    init(repo: any BoardItemRepository) { self.repo = repo }
    func load() async {
        state = .loading
        do { let items = try await repo.list(); state = items.isEmpty ? .empty : .data(items) }
        catch { state = .failed(error.localizedDescription) }
    }
}

struct ItemListScreen: View {
    @State private var model: ItemListModel
    init(model: ItemListModel) { _model = State(initialValue: model) }
    var body: some View {
        Group {
            switch model.state {
            case .loading: AppSpinner()
            case .empty: ContentUnavailableView("No items yet", systemImage: "tray")
            case .failed(let message):
                ContentUnavailableView {
                    Label("Couldn't load items", systemImage: "exclamationmark.triangle")
                } description: { Text(message) } actions: {
                    Button("Try again") { Task { await model.load() } }
                }
            case .data(let items): ItemList(items: items)
            }
        }
        .task { await model.load() }
    }
}

Typechecks with swiftc -swift-version 6 (verified). The original example's domain struct Task shadowed _Concurrency.Task, giving error: missing argument for parameter 'title' in call at every Task { } call site — renamed to BoardItem here. Note the explicit .empty case rendered with ContentUnavailableView and a retry action on .failed, and the init that seeds State(initialValue:) so the model can be injected.

Previews at the matrix (typechecked against the iOS 18 simulator SDK):

#Preview("Phone, AX3, dark") { ItemListScreen(model: .preview).environment(\.dynamicTypeSize, .accessibility3).preferredColorScheme(.dark) }
#Preview("Landscape", traits: .landscapeLeft) { ItemListScreen(model: .preview) }

For a layout that must switch at large accessibility sizes: typeSize.isAccessibilitySize ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout()). For an icon-only control: Button("Add to cart", systemImage: "cart.badge.plus", action:) with .labelStyle(.iconOnly) and .frame(minWidth: 44, minHeight: 44) (see mobile-accessibility).

Rules for testing

  • Unit-test the @Observable model with a mocked repository (no UI).
  • New tests in Swift Testing (import Testing, @Test, #expect) where the target already uses Xcode 16+; XCTest stays for existing UI tests and app.performAccessibilityAudit(). Test-first.

Modern API

  • foregroundStyle (not foregroundColor), .clipShape(.rect(cornerRadius:)) (not .cornerRadius), the Tab API for tab bars, the two-parameter onChange(of:) { old, new in }, .topBarTrailing placement, containerRelativeFrame instead of GeometryReader when it covers the case, never UIScreen.main.bounds.
  • Color.withValues is not a thing in SwiftUI (that's the Flutter equivalent) — use asset-catalog colours or Color(.sRGB, ...)/.opacity() as the repo already does.

Liquid Glass (iOS 26+)

  • Standard bars, tab views and sheets adopt Liquid Glass automatically when built with Xcode 26+. Don't paint an opaque custom background over a toolbar/tab bar to "fix" this.
  • Use .glassEffect only on custom floating controls, and only when the task asks for it.
  • UIDesignRequiresCompatibility is ignored when building with the iOS 27 SDK — don't rely on it to opt back out of the new look.

Common Mistakes

  • Networking or business rules inside body / View.
  • @ViewBuilder helper funcs instead of real subviews.
  • Hardcoded colors/sizes instead of asset-catalog + tokens.
  • No previews, so every change needs a full build to see.
  • A domain model named Task.
  • GCD calls in new code instead of async/await.

Red Flags

  • A View with a URLSession call in it.
  • Magic Color(red:...) and paddings sprinkled across views.
  • A 200-line body.
  • A type named Task anywhere in the diff.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

See all skills by makifbaysal

Report a problem with this skill