本文へ移動
cccskills
無料GitHub で公開

layout

SwiftUI layout beyond stacks — the Layout protocol (when custom layout beats GeometryReader), Grid vs lazy grids, custom containers with sections and container values, and lazy-stack/ScrollView performance rules (what breaks laziness, prefetch discipline, scroll APIs). Use when building custom layouts or containers, fixing lazy-stack jank or memory growth, or wiring programmatic/snapping scrolling.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md7.0 KB

SKILL.md(原文)

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

SwiftUI Layout & Containers

The layer between "stacks and spacers" and "it scrolls like butter with 100k rows" — Apple's Layout protocol, container composition, and the lazy-stack rules from the WWDC26 deep dive. View identity/data-flow questions route to swiftui/data-flow.

When This Skill Activates

  • "Make these buttons equal width" / measurement-dependent layout
  • Building a reusable container (custom List/board/carousel) that should accept ForEach + sections
  • Lazy stack jank, memory growth, scroll-position bugs, broken scroll targeting
  • Programmatic scrolling, paging/snapping, scroll-linked effects
  • GeometryReader causing layout loops or mangled sizing

Custom Layout protocol (not GeometryReader)

Reach for a custom Layout whenever you must measure subviews and feed the measurement back into layout — GeometryReader only measures its container and can't influence the engine. Canonical case: equal-width buttons.

  • sizeThatFits: propose .unspecified to read each subview's ideal size (subviews.map { $0.sizeThatFits(.unspecified) }); guard empty subviews; replacingUnspecifiedDimensions() for nil proposal dimensions.
  • placeSubviews: never assume origin (0,0) — use bounds.minX/midX (non-zero origins are what make layouts composable); place(at:anchor:proposal:) with a proposal that may differ from the ideal size (that's how equal widths happen).
  • Respect spacing preferences: subviews[i].spacing.distance(to:along:), taking the larger of conflicting preferences — matching built-in containers. No hardcoded 8s.
  • Per-subview data via LayoutValueKey (+ a layoutValue convenience modifier), read as subview[Key.self].
  • Cache only after Instruments shows layout cost — it's an optimization, not a requirement.
  • Switch layouts without killing identity: AnyLayout(HStackLayout()) ↔ custom layout with .animation(_:value:) — SwiftUI sees one changing view, so state survives and it animates.
  • Don't build fallbacks into the layout — wrap alternatives in ViewThatFits.

Grid decisions

NeedUse
Static 2D with cross-row alignmentGrid/GridRow (+ gridCellColumns to span, gridColumnAlignment per column)
Scrollable, large contentLazyVGrid/LazyHGrid (only visible views load; one axis fixed up front)
"First arrangement that fits"ViewThatFits

Custom containers (Demystify Containers)

Make containers that compose like List does:

  • API shape: a trailing @ViewBuilder var content: Content — callers can then mix static views, ForEach, and conditionals.
  • Iterate resolved children with ForEach(subviews: content); need the whole collection (count/chunking)? Group(subviews: content) { subviews in … }.
  • Internalize declared vs resolved: one declared ForEach resolves to N subviews; Group to its children; EmptyView to zero; if conditionally. Counting declared views is a bug.
  • Sections are opt-in: ForEach(sections: content), reading section.header / section.content; check header.isEmpty before rendering the slot.
  • Per-child customization via container values: extension ContainerValues { @Entry var … }, set with a convenience modifier, read via subview.containerValues. Scoping model: Environment flows down · Preferences flow up · container values reach only the direct container. Setting one on a Section styles the whole section.

Lazy stacks & scrolling performance (WWDC26 rules)

LazyVStack builds views only until the viewport fills; totals and offsets are estimated from average placed-view size and corrected as you scroll. Everything below follows from that:

  • One subview per ForEach element, always. An if inside a row (0-or-1 views) forces the stack to keep off-screen views + their @State alive to preserve indices — and environment changes then re-evaluate off-screen bodies. Filter at the data layer (@Query predicate); gate auth-type conditions outside the stack.
  • Never key logic off absolute scroll offset in a lazy stack (onScrollGeometryChange sees estimates) — use onScrollTargetVisibilityChange(threshold: 0.8) for visibility triggers.
  • Set up in init, not onAppear (_model = State(initialValue:)): body runs during prefetch; onAppear fires only on-screen, throwing prefetch work away and causing post-appearance size jumps. Start async loads in init/task.
  • Don't persist meaningful state in row @State — off-screen views are eventually released. Hoist (@State var highlighted: Set<ID> outside, @Binding down).
  • scrollTransition transforms must stay inside the original frame (scale ✅; rotations escaping the frame make views vanish early).
  • Don't drive layout from onGeometryChange height feedback (content shoves, targeting breaks) — that's the custom Layout case above.
  • Nest LazyHStack inside LazyVStack freely (unscrolled rows stay unloaded) — but fix child heights (lineLimit, explicit frames) in the horizontal stacks.
  • pinnedViews: [.sectionHeaders] pins headers; infinite scroll = trailing ProgressView().onAppear { fetchNextPage() } after the ForEach.

The scroll API map

  • Snapping/paging: scrollTargetLayout() + scrollTargetBehavior(.viewAligned/.paging).
  • Track/control position: scrollPosition binding; programmatic ScrollPosition + scrollTo(id:) — works for unloaded targets if IDs map to stable one-subview elements.
  • Scroll-linked effects: scrollTransition (enter/leave viewport) and visualEffect (geometry without GeometryReader) — details in design/animation-patterns.
  • Reactions: onScrollGeometryChange (fine outside lazy estimation), onScrollVisibilityChange (autoplay/analytics).
  • Performance floor: list/scroll internals were rewritten (WWDC25) — macOS lists ~6× faster at 100k+ rows, and lazy loading works in nested ScrollView+LazyVStack; profile with the SwiftUI instrument (performance/swiftui-debugging).

Output Format

Layout review: Symptom | Rule violated | Fix — check the one-subview-per-element rule first in any lazy-stack complaint; it explains most jank, memory growth, and targeting bugs.

References

レビュー

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

同じリポジトリのスキル

概要と使いどころ

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.

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

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

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

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.

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

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.

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

alarmkit

無料

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.

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

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.

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

rshankras/claude-code-apple-skills7912026年7月24日 更新

rshankras のスキルをすべて見る

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