本文へ移動
cccskills
無料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

インストール方法を見る

含まれるファイル(1)

  • SKILL.md6.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

Use when deciding whether a request needs an analiz task before implementation - the conditions that require the architect's analysis versus going straight to implementation

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

makifbaysal のスキルをすべて見る

このスキルの問題を報告する