swiftship/internal/skills/data/always/layout/SKILL.md
Layout patterns: VStack/HStack/ZStack composition, view structure, subview extraction, GeometryReader alternatives, safe area handling. Use when arranging views, building screen layouts, or structuring view hierarchies. Triggers: VStack, HStack, ZStack, LazyVStack, Grid, Spacer, padding, frame, GeometryReader.
npx skillsauth add abdullah4ai/apple-developer-toolkit layoutInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guide to stack layouts, view composition, subview extraction, and layout best practices.
// Good - relative to actual layout
GeometryReader { geometry in
VStack {
HeaderView()
.frame(height: geometry.size.height * 0.2)
ContentView()
}
}
// Avoid - magic numbers that don't adapt
VStack {
HeaderView()
.frame(height: 150) // Doesn't adapt to different screens
ContentView()
}
Views should work in any context. Never assume presentation style or screen size.
// Good - adapts to given space
struct ProfileCard: View {
let user: User
var body: some View {
VStack {
Image(user.avatar)
.resizable()
.aspectRatio(contentMode: .fit)
Text(user.name)
Spacer()
}
.padding()
}
}
// Avoid - assumes full screen
Image(user.avatar)
.frame(width: UIScreen.main.bounds.width) // Wrong!
Custom views should own static containers but not lazy/repeatable ones.
// Good - owns static container
struct HeaderView: View {
var body: some View {
HStack {
Image(systemName: "star")
Text("Title")
Spacer()
}
}
}
SwiftUI's diffing algorithm compares view hierarchies to determine what needs updating.
// Good - same view, different states
SomeView()
.opacity(isVisible ? 1 : 0)
// Avoid - creates/destroys view identity
if isVisible {
SomeView()
}
Use conditionals when you truly have different views:
// Correct - fundamentally different views
if isLoggedIn {
DashboardView()
} else {
LoginView()
}
// BAD - re-executes complexSection() on every tap
struct ParentView: View {
@State private var count = 0
var body: some View {
VStack {
Button("Tap: \(count)") { count += 1 }
complexSection() // Re-executes every tap!
}
}
@ViewBuilder
func complexSection() -> some View {
ForEach(0..<100) { i in
HStack {
Image(systemName: "star")
Text("Item \(i)")
}
}
}
}
// GOOD - ComplexSection body SKIPPED when its inputs don't change
struct ParentView: View {
@State private var count = 0
var body: some View {
VStack {
Button("Tap: \(count)") { count += 1 }
ComplexSection() // Body skipped during re-evaluation
}
}
}
struct ComplexSection: View {
var body: some View {
ForEach(0..<100) { i in
HStack {
Image(systemName: "star")
Text("Item \(i)")
}
}
}
}
// BAD - closure prevents SwiftUI from skipping updates
struct MyContainer<Content: View>: View {
let content: () -> Content
var body: some View {
VStack { Text("Header"); content() }
}
}
// GOOD - view can be compared
struct MyContainer<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
VStack { Text("Header"); content }
}
}
Use ZStack to compose multiple peer views that should be layered together.
Prefer overlay / background when decorating a primary view.
// GOOD - decoration in overlay
Button("Continue") { }
.overlay(alignment: .trailing) {
Image(systemName: "lock.fill")
.padding(.trailing, 8)
}
// GOOD - background shape takes parent size
HStack(spacing: 12) {
Image(systemName: "tray")
Text("Inbox")
}
.background {
Capsule()
.strokeBorder(.blue, lineWidth: 2)
}
// Bad - deep nesting, excessive layout passes
VStack { HStack { VStack { HStack { Text("Deep") } } } }
// Good - flatter hierarchy
VStack { Text("Shallow"); Text("Structure") }
// Good - single geometry reader or containerRelativeFrame
containerRelativeFrame(.horizontal) { width, _ in
width * 0.8
}
// Good - gate by threshold
.onPreferenceChange(ViewSizeKey.self) { size in
let difference = abs(size.width - currentSize.width)
if difference > 10 { currentSize = size }
}
// Good - logic in testable model (iOS 17+)
@Observable
@MainActor
final class LoginViewModel {
var email = ""
var password = ""
var isValid: Bool {
!email.isEmpty && password.count >= 8
}
func login() async throws { }
}
struct LoginView: View {
@State private var viewModel = LoginViewModel()
var body: some View {
Form {
TextField("Email", text: $viewModel.email)
SecureField("Password", text: $viewModel.password)
Button("Login") {
Task { try? await viewModel.login() }
}
.disabled(!viewModel.isValid)
}
}
}
// Good - action references method
struct PublishView: View {
@State private var viewModel = PublishViewModel()
var body: some View {
Button("Publish Project", action: viewModel.handlePublish)
}
}
Use @Environment(\.horizontalSizeClass) for layout decisions — never device checks:
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
| Context | horizontalSizeClass |
|---|---|
| iPad full-screen (any orientation) | .regular |
| iPad Split View (narrow) | .compact |
| iPad Split View (wide) | .regular |
Critical: iPad in Split View can report .compact — always use size classes, never UIDevice.current.
@Environment(\.horizontalSizeClass) private var sizeClass
var body: some View {
let layout = sizeClass == .compact
? AnyLayout(VStackLayout(spacing: 16))
: AnyLayout(HStackLayout(spacing: 24))
layout {
ContentBlockA()
ContentBlockB()
}
}
Always use GridItem(.adaptive(minimum:maximum:)) — automatically adjusts columns:
LazyVGrid(
columns: [GridItem(.adaptive(minimum: 160, maximum: 320))],
spacing: 16
) {
ForEach(items) { item in CardView(item: item) }
}
.padding()
Constrain text content width on iPad to maintain readability:
ScrollView {
content
.frame(maxWidth: 700)
.frame(maxWidth: .infinity)
}
Forms should not stretch full-width on iPad:
@Environment(\.horizontalSizeClass) private var sizeClass
Form {
Section("General") { /* ... */ }
}
.formStyle(.grouped)
.frame(maxWidth: sizeClass == .regular ? 600 : .infinity)
Use when a component should pick the best layout for available space:
ViewThatFits {
HStack(spacing: 16) { icon; title; subtitle; actionButton }
VStack(alignment: .leading, spacing: 8) {
HStack { icon; title }; subtitle; actionButton
}
}
Prefer containerRelativeFrame over GeometryReader:
Image("photo")
.containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16)
UIDevice.current, UIScreen.main.bounds, or #if targetEnvironment for layoutGridItem(.adaptive(minimum:)) for grids.leading/.trailing — never .left/.right@ScaledMetric for custom dimensions that respect Dynamic Typetools
Apple platform skill for docs, WWDC lookup, App Store Connect work, and SwiftUI app generation. Use repo-local `node cli.js` for Apple docs and WWDC search, `appledev store` for App Store Connect workflows, and `appledev build` for app scaffolding or fix loops on macOS. USE WHEN: Apple APIs, WWDC sessions, TestFlight/App Store tasks, or building/fixing Apple-platform apps. DON'T USE WHEN: non-Apple platforms, generic backend work, or general web research. EDGE CASES: docs-only queries use `node cli.js` in this repo, not `appledev`; release workflows use `appledev store`; app scaffolding uses `appledev build`; rules-only requests can read `references/ios-rules/` or `references/swiftui-guides/` progressively without invoking binaries.
tools
All-in-one Apple developer skill with three integrated tools shipped as a single unified binary. (1) Documentation search across Apple frameworks, symbols, and 1,267 WWDC sessions from 2014-2025. No credentials needed. (2) App Store Connect CLI with 120+ commands covering builds (find/wait/upload), TestFlight, pre-submission validate, submissions, signing, subscriptions (family-sharable), IAP, analytics, Xcode Cloud, metadata workflows, release pipeline dashboard, insights, win-back offers, promoted purchases, product pages, nominations, accessibility declarations, pre-orders, pricing filters, localizations update, diff, webhooks with local receiver, workflow automation, and more. Requires App Store Connect API key. (3) Multi-platform app builder (iOS/watchOS/tvOS/iPad/macOS/visionOS) that generates complete Swift/SwiftUI apps from natural language with auto-fix, simulator launch, interactive chat mode, and open-in-Xcode. Requires an LLM API key and Xcode. Includes 38 iOS development rules and 12 SwiftUI best practice guides for Liquid Glass, navigation, state management, and modern APIs. All three tools ship as one binary (appledev). USE WHEN: Apple API docs, App Store Connect management, WWDC lookup, or building iOS/watchOS/tvOS/macOS/visionOS apps from scratch. DON'T USE WHEN: non-Apple platforms or general coding.
testing
watchOS complications: WidgetKit complication families, accessory sizes, timeline providers for watch face. Use when implementing watchOS-specific patterns related to widgets.
development
watchOS haptic feedback: WKInterfaceDevice preset haptic types for wrist-based feedback. Use when implementing watchOS-specific patterns related to haptics.