All skills
dpearson2699 avatar

/swiftui-layout-components

@45c9085

Build SwiftUI layouts using stacks, grids, lists, scroll views, forms, and controls. Covers VStack/HStack/ZStack, LazyVGrid/LazyHGrid, List with sections and swipe actions, ScrollView with ScrollPosition and scroll-driven reveal surfaces, Form with validation, Toggle/Picker/Slider, .searchable, and overlay patterns. Use when building data-driven layouts, collection views, paged detail reveals, settings screens, search interfaces, or transient overlay UI.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/swiftui-layout-components

This session only. Nothing lands on disk.

referencesform.md

≈766 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Form

Intent

Use Form for structured settings, grouped inputs, and action rows. This pattern keeps layout, spacing, and accessibility consistent for data entry screens.

Core patterns

  • Wrap the form in a NavigationStack only when it is presented in a sheet or standalone view without an existing navigation context.
  • Group related controls into Section blocks.
  • Use .scrollContentBackground(.hidden) plus a custom background color when you need design-system colors.
  • Apply .formStyle(.grouped) for grouped styling when appropriate.
  • Use @FocusState to manage keyboard focus in input-heavy forms.

Example: settings-style form

@MainActor
struct SettingsView: View {
  @Environment(Theme.self) private var theme

  var body: some View {
    NavigationStack {
      Form {
        Section("General") {
          NavigationLink("Display") { DisplaySettingsView() }
          NavigationLink("Haptics") { HapticsSettingsView() }
        }

        Section("Account") {
          Button("Edit profile") { /* open sheet */ }
            .buttonStyle(.plain)
        }
        .listRowBackground(theme.primaryBackgroundColor)
      }
      .navigationTitle("Settings")
      .navigationBarTitleDisplayMode(.inline)
      .scrollContentBackground(.hidden)
      .background(theme.secondaryBackgroundColor)
    }
  }
}

Example: modal form with validation

@MainActor
struct AddRemoteServerView: View {
  @Environment(\.dismiss) private var dismiss
  @Environment(Theme.self) private var theme

  @State private var server: String = ""
  @State private var isValid = false
  @FocusState private var isServerFieldFocused: Bool

  var body: some View {
    NavigationStack {
      Form {
        TextField("Server URL", text: $server)
          .keyboardType(.URL)
          .textInputAutocapitalization(.never)
          .autocorrectionDisabled()
          .focused($isServerFieldFocused)
          .listRowBackground(theme.primaryBackgroundColor)

        Button("Add") {
          guard isValid else { return }
          dismiss()
        }
        .disabled(!isValid)
        .listRowBackground(theme.primaryBackgroundColor)
      }
      .formStyle(.grouped)
      .navigationTitle("Add Server")
      .navigationBarTitleDisplayMode(.inline)
      .scrollContentBackground(.hidden)
      .background(theme.secondaryBackgroundColor)
      .scrollDismissesKeyboard(.immediately)
      .toolbar { CancelToolbarItem() }
      .onAppear { isServerFieldFocused = true }
    }
  }
}

Design choices to keep

  • Prefer Form over custom stacks for settings and input screens.
  • Keep rows tappable by using .contentShape(Rectangle()) and .buttonStyle(.plain) on row buttons.
  • Use list row backgrounds to keep section styling consistent with your theme.

Pitfalls

  • Avoid heavy custom layouts inside a Form; it can lead to spacing issues.
  • If you need highly custom layouts, prefer ScrollView + VStack.
  • Don’t mix multiple background strategies; pick either default Form styling or custom colors.

Source: SKILL.md on GitHub

1 warning17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill is a set of instructional documentation and Swift code patterns for building SwiftUI layouts. It contains no executable scripts, external dependencies, or network operations, and no security risks were identified during the analysis.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: MEDIUM · 1 issue

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 45c9085. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 3 months ago
  • swiftui
  • ios
  • layout
  • vstack
  • hstack
  • zstack
  • lazyvgrid
  • lazyhgrid
  • list
  • form
  • picker
  • toggle
  • slider
  • searchable
  • scrollview

README badge

README badge for dpearson2699/swift-ios-skills/swiftui-layout-components

Provides SwiftUI layout patterns for iOS 17+ apps: VStack/HStack/ZStack, LazyVGrid/LazyHStack, List with sections, ScrollView with ScrollPosition, Form with validation controls, search, and overlay UI. Use when building data-driven layouts, collection views, settings screens, or search interfaces.

Generated from the current SKILL.md.

What iOS versions does this skill cover?
Targets iOS 26+ with Swift 6.3, with patterns backward-compatible to iOS 17 unless explicitly noted (e.g., `.scrollEdgeEffectStyle` is iOS 26 only).
When should I use LazyVStack vs regular VStack?
Use regular VStack for small, fixed content like headers and forms. Use LazyVStack for large or dynamic collections (feeds, chat messages) inside ScrollView to render child views on demand.
Can I use GeometryReader inside lazy containers?
No. Placing GeometryReader inside lazy containers forces eager measurement and defeats lazy loading. Use `.onGeometryChange` (iOS 16+) if you need to read dimensions.
What's the difference between List and ScrollView with LazyVStack?
List provides built-in row reuse, selection, and accessibility for feeds and settings. ScrollView with LazyVStack offers custom layout flexibility for mixed content and horizontal scrolling. Don't nest them in the same hierarchy.
How do I debounce search input?
Use `.task(id: searchQuery)` to trigger async search only when the query changes, and add a small delay (e.g., `Task.sleep(for: .milliseconds(250))`) before fetching to avoid overfetching.

Generated from the current SKILL.md. These answers refresh after source changes.