swiftui-macos-form-alignment
SwiftUI macOS Form layout: alignment override bug, background control, and VStack-based alternative. Load when debugging alignment issues inside Form rows, or when row controls (toggles, text fields, pickers) are visually misaligned with their labels on macOS.
- License 许可
- MIT
- 最近更新
- 2026-10-25
- Stars
- ★ 1
- Forks
- 0
01 前言 Front Matter
- name
- swiftui-macos-form-alignment
- description
- SwiftUI macOS Form layout: alignment override bug, background control, and VStack-based alternative. Load when debugging alignment issues inside Form rows, or when row controls (toggles, text fields, pickers) are visually misaligned with their labels on macOS.
03 SKILL.md 正文
SwiftUI macOS Form Alignment
The Problem
Form { }.formStyle(.grouped) on macOS renders using NSTableView internally. Each direct child of a Section is wrapped in a fixed-height table row cell that center-aligns its content vertically, regardless of any alignment modifiers you put on the view inside the row.
This means:
// ❌ Does NOT work — HStack(alignment: .top) is ignored by the Form row container
Section {
HStack(alignment: .top) {
VStack { Text(label); Text(description) } // tall due to description
Spacer()
TextField(...) // shorter — ends up centered, visually below label
}
}
No matter what alignment you apply to the HStack (.top, .firstTextBaseline), or whether you use .overlay(alignment: .topTrailing), the Form's row container wins and centers the whole thing.
Diagnosing Alignment Issues
Add background colors to every subview to see actual frame extents at runtime:
Text(label).background(Color.red.opacity(0.4))
TextField(...).background(Color.blue.opacity(0.4))
HStack { ... }.background(Color.green.opacity(0.4))
If the colored boxes show the layout you want but the controls still appear in the wrong place, the Form container is the culprit — not your alignment modifiers.
Fix 1: Restructure to put label and control on the same line
Put the control on the same line as the label (not as a sibling of a tall VStack), and move the description to its own line below. With both items in the same HStack, there is no height mismatch and center alignment looks correct:
// ✓ Label and control in the same HStack — always at the same height
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .top) {
Text(label).font(...)
Spacer()
HStack(alignment: .center, spacing: 4) {
Text(modifier)
TextField(...)
}
}
Text(description).font(.caption).foregroundStyle(.secondary)
}
.padding(.vertical, 8)
Fix 2: Replace Form with ScrollView + VStack (most reliable)
When Fix 1 is insufficient (e.g. complex multi-row alignment), ditch Form entirely and use a plain ScrollView + VStack. Style the sections manually to match the grouped form appearance:
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Section
VStack(alignment: .leading, spacing: 6) {
Text("Section Header")
.font(.footnote)
.foregroundStyle(.secondary)
.textCase(.uppercase)
.padding(.leading, 4)
VStack(spacing: 0) {
MyRow(...)
Divider().padding(.leading, 16)
MyRow(...)
}
.background(Color(NSColor.controlBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
.padding(20)
}
Each MyRow owns its own horizontal padding (.padding(.horizontal, 16)) since Form no longer provides it.
Without Form, alignment modifiers work exactly as documented. HStack(alignment: .top) top-aligns all children. No surprises.
Form Background Control
Form { }.formStyle(.grouped) fills the content area with its own background color, which differs from NSColor.windowBackgroundColor. To unify backgrounds across pages that mix Form and non-Form layouts:
ScrollView {
Form { ... }
.formStyle(.grouped)
.scrollContentBackground(.hidden) // hides Form's internal scroll background
}
.background(Color(NSColor.windowBackgroundColor)) // shows window background in the gap areas
.scrollContentBackground(.hidden) requires macOS 13+. It hides the scrollable view's background but leaves section cell backgrounds intact — sections appear as lighter cards on the darker window background.
When to Use Form vs VStack
| Situation | Use |
|---|---|
| Simple toggle/checkbox rows | Form — center alignment looks fine when label ≈ control height |
| Picker or segmented control rows | Form — controls are tall enough that centering is acceptable |
| TextField rows where label top must align with field top | VStack + ScrollView |
Any row needing .top or .firstTextBaseline alignment |
VStack + ScrollView |
| Rows with multi-line descriptions AND right-side controls | VStack + ScrollView |
04 同仓库更多 Skills
workflow
Development workflow for this repo: plan → test → implement → human review → repeat. Load when starting any feature, bug fix, or refactor.
usernotifications-documentation
UserNotifications official doc index — load when building notification features, requesting authorization, scheduling local notifications, handling notification actions, or any UserNotifications framework behavior.
swiftui-documentation
SwiftUI official doc index — load when building SwiftUI views, modifiers, state management, data flow, AppKit bridging, or any SwiftUI behavior.
swifterm-documentation
SwiftTerm official doc index — load when building SwiftTerm features, terminal emulation, terminal views, process lifecycle, delegate callbacks, GPU rendering, pseudo-terminals, or any SwiftTerm-specific behavior. Any project that uses or interacts with SwiftTerm should load this skill.
sqlite-documentation
SQLite official doc index — load when working with SQLite, the C/C++ API, import SQLite3, prepared statements, binding, query execution, result codes, or any SQLite-specific behavior. Any project that uses or interacts with SQLite should load this skill.
ship
Commit, push, ensure a PR exists, capture screenshots, and attach them to the PR.