Skip to content
cccskills
FreePublished on GitHub

data-flow

SwiftUI's actual mental model — view identity, lifetime, and dependencies (the Demystify canon), state ownership decision rules, Observation's per-property tracking, body-performance discipline, and the main-actor concurrency contract. Use when state resets mysteriously, views re-render too often, animations glitch between branches, choosing @State vs @Bindable vs plain property, or debugging "why did body run."

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

See how to install

Files included (1)

  • SKILL.md8.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

SwiftUI Data Flow

Nearly every confusing SwiftUI bug — state that resets, animations that crossfade instead of move, lists that flash, bodies that run constantly — traces to identity, lifetime, or dependencies. This is Apple's own mental model (the Demystify sessions + Data Essentials + Observation), current through the WWDC26 @State macro.

When This Skill Activates

  • "@State resets when…" / state loses its value on a condition change
  • Views re-render too often; animations crossfade when they should move
  • Lists flashing, rows reordering wrongly, ForEach misbehaving
  • Choosing between @State, @Binding, @Bindable, @Environment, plain property
  • Debugging with Self._printChanges(); concurrency warnings in view code

Identity: the root concept

SwiftUI sees three things: identity, lifetime, dependencies. Views with the same identity are "different states of the same conceptual UI element"; distinct identities are distinct views.

  • Structural identity = type + position in the hierarchy. An if/else creates two identities (_ConditionalContent) — flipping the branch destroys/recreates the view: state resets, transitions crossfade instead of animating.

  • Explicit identity = id: in ForEach or .id(_:) (also the target for ScrollViewReader.scrollTo). Changing an explicit id is a new identity — new lifetime, fresh state. (That's the .id(item.id) force-refresh trick — use it knowingly.)

  • The inert-modifier rule (the most under-used fix): prefer one view whose modifiers vary over branching —

    // ❌ two identities; state resets, transition crossfades
    if expired { content.opacity(0.3) } else { content }
    // ✅ one identity; cheap, pruned when inert
    content.opacity(expired ? 0.3 : 1.0)
    

    Inert values (opacity 1, padding 0) cost nothing. "By default, try to preserve identity."

  • Conditionally include a view inside a stack rather than conditionally wrapping the stack.

Lifetime: state is tied to identity

  • View values are ephemeral — created for comparison, then destroyed. Never rely on the struct instance; identity provides continuity.
  • "Whenever the identity changes, the state is replaced" — @State/@StateObject storage tears down and reinitializes. If state "randomly resets," find the identity change.
  • WWDC26: @State is a macro with lazy initialization of @Observable classes (backported to iOS 17) — the stored object initializes once per lifetime, not on every view-value init. Remove default values when also assigning in init (source-breaking edge).

ForEach identifier rules (the flashing-list checklist)

  • Stable — never var id = UUID() computed per access (everything flashes/reanimates).
  • Not indices — insert-at-front reads as insert-at-end; rows animate wrongly.
  • Unique — duplicate IDs drop rows.
  • Use persistent/database-derived IDs; that's what Identifiable is for. Range ForEach (0..<n) only with a constant range.
  • Constant views per element: an if filter inside ForEach (0-or-1 views) or AnyView forces List to resolve every row just to count them. Filter in the data, and cache the filtered collection in the model — an inline .filter re-runs linearly on every body.
  • List/Table gather all identifiers eagerly — cheap IDs = fast loads.

Dependencies: the graph, not the tree

  • Every piece of data read in body is a dependency; only views whose dependency changed re-run, and value comparison prunes unchanged subtrees. Stable identity is "the backbone of the dependency graph."
  • Scope dependencies tightly: pass the subview what it renders (the Image, not the whole model). Extracting subviews is free — "breaking up one view into multiple doesn't hurt performance" — and shrinks invalidation scope.
  • Observation (@Observable) tracks per property, per instance — a view re-renders only when a property it actually read changes, including through computed properties, arrays, optionals, and nesting.
  • Migration from ObservableObject: drop conformance + @Published → @Observable; @ObservedObject → delete or @Bindable; @EnvironmentObject → @Environment. Invalidation narrows from whole-object to read-properties — a free performance win.

State ownership: the decision rules

Ask Apple's three questions: what data does the view need · how does it manipulate it · where does truth live?

SituationUse
Display-only, parent owns itplain let property
Transient, view-local UI state@State (group related fields into one struct with mutating methods)
Write access to someone else's truth@Binding (bindings compose: $config.note)
Observable model owned by this view@State (lazy-init since WWDC26 macro)
Observable model, needs $model.field bindings only@Bindable
Observable model, globally available@Environment
Observable model, none of the aboveplain property
  • ❌ Never allocate a reference-type model inline as an @ObservedObject default — every re-run reallocates it (heap churn, data loss); use @StateObject or @State + @Observable.
  • ❌ Two siblings each holding @State for the same value desync — lift state to the container and hand children Bindings.
  • @SceneStorage (restoration state, per window) and @AppStorage (settings) are stores next to your model, not the model. Limit total sources of truth.

Body discipline

  • Body must be a pure function, free of side effects — no allocation, I/O, filtering, or string-building; move loading to .task { await … }.
  • Debug why body ran with Self._printChanges() (or expression Self._printChanges() at an LLDB breakpoint): @self = view value changed; a named property = that dependency changed. Debug-only — never ship it. Deeper workflow: performance/swiftui-debugging.
  • ❌ AnyView hides structure from SwiftUI (worse diagnostics/performance) — use @ViewBuilder helpers and switch instead.

Concurrency contract (WWDC25)

  • View is @MainActor: body, @State, members, and Task { } created in body are all main-actor — most view code needs zero annotations (and Swift 6.2's default-isolation mode removes the rest).
  • SwiftUI runs some of your closures off-main — Shape.path(in:), Layout methods, visualEffect, onGeometryChange — that's why they're Sendable. Don't touch self.someState there; copy the value in the capture list ([pulse]) and compute from the proxies SwiftUI hands you.
  • Every await can resume after the frame deadline, so time-sensitive state (gesture/scroll reactions, button loading indicators) must mutate synchronously before starting async work. Bridge UI↔async through a piece of state; keep view Tasks minimal ("inform the model") so async logic stays unit-testable.

Output Format

Data-flow review: Symptom | Root cause (identity / lifetime / dependency / ownership) | Fix — check identity first; it explains most of the rest.

References

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

Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

Generate accessibility infrastructure for VoiceOver, Dynamic Type, and accessibility features. Use when improving app accessibility, adding accessibility labels and hints, or auditing compliance.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

Generates an Apple-compliant account deletion flow with multi-step confirmation UI, optional data export, configurable grace period, Keychain cleanup, and server-side deletion request. Use when user needs account deletion, right-to-delete, or Apple App Review compliance for account removal.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

Privacy-preserving ad measurement with AdAttributionKit (SKAdNetwork's successor) — install and re-engagement attribution, conversion-value strategy under crowd anonymity, and end-to-end postback testing. Use when running paid acquisition beyond Apple Ads, measuring re-engagement campaigns, designing conversion values, or migrating from SKAdNetwork.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

AlarmKit integration for scheduling alarms and timers with custom UI, Live Activities, and snooze support. Use when implementing alarm or timer features in iOS 26+ apps.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

Interpret app metrics and make data-driven decisions. Covers DAU/MAU, retention, LTV, ARPU, App Store Connect analytics, AARRR funnel analysis, cohort analysis, and diagnostic decision trees. Use when user wants to understand their metrics, diagnose problems, or build a data-driven growth plan.

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

rshankras/claude-code-apple-skills812Updated Jul 24, 2026

See all skills by rshankras

Report a problem with this skill