본문으로 건너뛰기
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.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

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/tasktrooper1172026년 10월 20일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

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/tasktrooper1172026년 10월 20일 업데이트

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/tasktrooper1172026년 10월 20일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고