본문으로 건너뛰기
cccskills
무료GitHub에 공개

swiftui-view-refactor

Refactor and review SwiftUI view files with strong defaults for small dedicated subviews, MV-over-MVVM data flow, stable view trees, explicit dependency injection, and correct Observation usage. Use when cleaning up a SwiftUI view, splitting long bodies, removing inline actions or side effects, reducing computed `some View` helpers, or standardizing `@Observable` and view model initialization patterns.

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

설치 방법 보기

포함된 파일(2)

  • SKILL.md7.8 KB
  • references/mv-patterns.md5.4 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

SwiftUI View Refactor

Overview

Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one.

Core Guidelines

1) View ordering (top → bottom)

  • Enforce this ordering unless the existing file has a stronger local convention you must preserve.
  • Environment
  • private/public let
  • @State / other stored properties
  • computed var (non-view)
  • init
  • body
  • computed view builders / other view helpers
  • helper / async functions

2) Default to MV, not MVVM

  • Views should be lightweight state expressions and orchestration points, not containers for business logic.
  • Favor @State, @Environment, @Query, .task, .task(id:), and onChange before reaching for a view model.
  • Inject services and shared models via @Environment; keep domain logic in services/models, not in the view body.
  • Do not introduce a view model just to mirror local view state or wrap environment dependencies.
  • If a screen is getting large, split the UI into subviews before inventing a new view model layer.

3) Strongly prefer dedicated subview types over computed some View helpers

  • Flag body properties that are longer than roughly one screen or contain multiple logical sections.
  • Prefer extracting dedicated View types for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview.
  • Keep computed some View helpers rare and small. Do not build an entire screen out of private var header: some View-style fragments.
  • Pass small, explicit inputs (data, bindings, callbacks) into extracted subviews instead of handing down the entire parent state.
  • If an extracted subview becomes reusable or independently meaningful, move it to its own file.

Prefer:

var body: some View {
    List {
        HeaderSection(title: title, subtitle: subtitle)
        FilterSection(
            filterOptions: filterOptions,
            selectedFilter: $selectedFilter
        )
        ResultsSection(items: filteredItems)
        FooterSection()
    }
}

private struct HeaderSection: View {
    let title: String
    let subtitle: String

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(title).font(.title2)
            Text(subtitle).font(.subheadline)
        }
    }
}

private struct FilterSection: View {
    let filterOptions: [FilterOption]
    @Binding var selectedFilter: FilterOption

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                ForEach(filterOptions, id: \.self) { option in
                    FilterChip(option: option, isSelected: option == selectedFilter)
                        .onTapGesture { selectedFilter = option }
                }
            }
        }
    }
}

Avoid:

var body: some View {
    List {
        header
        filters
        results
        footer
    }
}

private var header: some View {
    VStack(alignment: .leading, spacing: 6) {
        Text(title).font(.title2)
        Text(subtitle).font(.subheadline)
    }
}

3b) Extract actions and side effects out of body

  • Do not keep non-trivial button actions inline in the view body.
  • Do not bury business logic inside .task, .onAppear, .onChange, or .refreshable.
  • Prefer calling small private methods from the view, and move real business logic into services/models.
  • The body should read like UI, not like a view controller.
Button("Save", action: save)
    .disabled(isSaving)

.task(id: searchText) {
    await reload(for: searchText)
}

private func save() {
    Task { await saveAsync() }
}

private func reload(for searchText: String) async {
    guard !searchText.isEmpty else {
        results = []
        return
    }
    await searchService.search(searchText)
}

4) Keep a stable view tree (avoid top-level conditional view swapping)

  • Avoid body or computed views that return completely different root branches via if/else.
  • Prefer a single stable base view with conditions inside sections/modifiers (overlay, opacity, disabled, toolbar, etc.).
  • Root-level branch swapping causes identity churn, broader invalidation, and extra recomputation.

Prefer:

var body: some View {
    List {
        documentsListContent
    }
    .toolbar {
        if canEdit {
            editToolbar
        }
    }
}

Avoid:

var documentsListView: some View {
    if canEdit {
        editableDocumentsList
    } else {
        readOnlyDocumentsList
    }
}

5) View model handling (only if already present or explicitly requested)

  • Treat view models as a legacy or explicit-need pattern, not the default.
  • Do not introduce a view model unless the request or existing code clearly calls for one.
  • If a view model exists, make it non-optional when possible.
  • Pass dependencies to the view via init, then create the view model in the view's init.
  • Avoid bootstrapIfNeeded patterns and other delayed setup workarounds.

Example (Observation-based):

@State private var viewModel: SomeViewModel

init(dependency: Dependency) {
    _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}

6) Observation usage

  • For @Observable reference types on iOS 17+, store them as @State in the owning view.
  • Pass observables down explicitly; avoid optional state unless the UI genuinely needs it.
  • If the deployment target includes iOS 16 or earlier, use @StateObject at the owner and @ObservedObject when injecting legacy observable models.

Workflow

  1. Reorder the view to match the ordering rules.
  2. Remove inline actions and side effects from body; move business logic into services/models and keep only thin orchestration in the view.
  3. Shorten long bodies by extracting dedicated subview types; avoid rebuilding the screen out of many computed some View helpers.
  4. Ensure stable view structure: avoid top-level if-based branch swapping; move conditions to localized sections/modifiers.
  5. If a view model exists or is explicitly required, replace optional view models with a non-optional @State view model initialized in init.
  6. Confirm Observation usage: @State for root @Observable models on iOS 17+, legacy wrappers only when the deployment target requires them.
  7. Keep behavior intact: do not change layout or business logic unless requested.

Notes

  • Prefer small, explicit view types over large conditional blocks and large computed some View properties.
  • Keep computed view builders below body and non-view computed vars above init.
  • A good SwiftUI refactor should make the view read top-to-bottom as data flow plus layout, not as mixed layout and imperative logic.
  • For MV-first guidance and rationale, see references/mv-patterns.md.

Large-view handling

When a SwiftUI view file exceeds ~300 lines, split it aggressively. Extract meaningful sections into dedicated View types instead of hiding complexity in many computed properties. Use private extensions with // MARK: - comments for actions and helpers, but do not treat extensions as a substitute for breaking a giant screen into smaller view types. If an extracted subview is reused or independently meaningful, move it into its own file.

리뷰

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

댓글

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

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

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

0 / 1,000자

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

같은 저장소의 스킬

When the user wants to A/B test App Store product page elements to improve conversion rate. Also use when the user mentions "A/B test", "product page optimization", "test my screenshots", "test my icon", "conversion rate optimization", "CPP", or "custom product pages". For screenshot design, see screenshot-optimization. For metadata optimization, see metadata-optimization.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

alarmkit

무료

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, and Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, AlarmButton stop and snooze actions, authorization, state observation, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

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

JordanCoin/ios-skills-collection62026년 9월 10일 업데이트

JordanCoin의 스킬 모두 보기

이 스킬의 문제 신고