使用堆疊、網格、列表、滾動視圖、表單和控制器來建構 SwiftUI 佈局。涵蓋 VStack/HStack/ZStack、LazyVGrid/LazyHGrid、帶有區段和滑動操作的 List、支援 ScrollPosition 和滾動驅動顯示區域的 ScrollView、含驗證的 Form、Toggle/Picker/Slider、.searchable 以及疊加模式。適用於建構資料驅動佈局、集合視圖、分頁細節顯示、設定畫面、搜尋介面或暫時性疊加 UI。
SwiftUI 佈局與元件
適用於 iOS 26+ 且使用 Swift 6.3 的 SwiftUI 應用程式之佈局與元件模式。涵蓋堆疊與網格佈局、列表模式、滾動視圖、表單、控制器、搜尋與疊加。除非另有說明,模式可向下相容至 iOS 17。
目錄
佈局基礎
標準堆疊
對於小型、固定大小的內容,使用 VStack、HStack 和 ZStack。它們會立即渲染所有子視圖。
VStack(alignment: .leading) {
Text(title).font(.headline)
Text(subtitle).font(.subheadline).foregroundStyle(.secondary)
}
惰性堆疊
在 ScrollView 內部使用 LazyVStack 和 LazyHStack 來處理大型或動態集合。它們會根據子視圖滾入視野時按需建立。
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
.padding(.horizontal)
}
何時使用哪一種:
- 非惰性堆疊: 小型、固定內容(標題、工具列、欄位較少的表單)
- 惰性堆疊: 大型或未知大小的集合、動態消息、聊天訊息
網格佈局
對於圖示選擇器、媒體圖庫和密集的視覺選擇,使用 LazyVGrid。使用 .adaptive 欄位來建立可跨裝置尺寸縮放的佈局,或使用 .flexible 欄位來設定固定的欄位數量。
// 自適應網格 — 欄位會自動調整以適應
let columns = [GridItem(.adaptive(minimum: 120, maximum: 1024))]
LazyVGrid(columns: columns) {
ForEach(items) { item in
ThumbnailView(item: item)
.aspectRatio(1, contentMode: .fit)
}
}
// 固定 3 欄網格
let columns = Array(repeating: GridItem(.flexible(minimum: 100), spacing: 4), count: 3)
LazyVGrid(columns: columns, spacing: 4) {
ForEach(items) { item in
ThumbnailView(item: item)
}
}
使用 .aspectRatio 來設定儲存格大小。切勿將 GeometryReader 放在惰性容器內部 — 它會強制立即測量並破壞惰性載入。如果需要讀取尺寸,請使用 .onGeometryChange(iOS 16+)。
完整的網格模式和設計選擇請參閱 references/grids.md。
列表模式
對於動態消息風格的內容和設定列,使用 List,因為它內建了列重用、選取和輔助功能。
List {
Section("一般") {
NavigationLink("顯示器") { DisplaySettingsView() }
NavigationLink("觸覺回饋") { HapticsSettingsView() }
}
Section("帳戶") {
Button("登出", role: .destructive) { }
}
}
.listStyle(.insetGrouped)
關鍵模式:
- 動態消息佈局使用
.listStyle(.plain),設定畫面使用.insetGrouped - 對於主題化表面,使用
.scrollContentBackground(.hidden)+ 自訂背景 - 使用
.listRowInsets(...)和.listRowSeparator(.hidden)來控制間距和分隔線 - 邊緣滾動: 使用
List+ScrollPosition搭配.scrollPosition($scrollPosition)來執行頂部/底部滾動操作 - 項目或區段跳轉: 使用
ScrollView+ 惰性堆疊搭配.scrollTargetLayout()和穩定的目標,以實現可靠的跳轉到 ID 行為 - 使用
.refreshable { }實現下拉重新整理動態消息 - 在應該可從頭到尾點擊的列上使用
.contentShape(Rectangle()) - 對於佈局審查或遷移指南,以容器選擇和約束為主;保持程式碼片段簡短,並將彈簧、過渡和時機選擇延後到
swiftui-animation
iOS 26: 使用 .scrollEdgeEffectStyle(.soft, for: .top) 實現現代滾動邊緣效果。
完整的列表模式(包括帶有滾動到頂部的動態消息列表)請參閱 references/list.md。
ScrollView
當你需要自訂佈局、混合內容或水平滾動時,使用 ScrollView 搭配惰性堆疊。
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(chips) { chip in
ChipView(chip: chip)
}
}
}
ScrollPosition: 啟用宣告式、雙向的滾動位置追蹤和程式化滾動。
@State private var scrollPosition = ScrollPosition(edge: .bottom)
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageRow(message: message)
}
}
.scrollTargetLayout()
}
.scrollPosition($scrollPosition)
.onChange(of: messages.last?.id) {
withAnimation { scrollPosition.scrollTo(edge: .bottom) }
}
safeAreaInset(edge:) 在不影響滾動佈局的情況下,將內容(輸入列、工具列)固定在鍵盤上方。
iOS 26 新增功能:
.scrollEdgeEffectStyle(.soft, for: .top)— 淡出邊緣效果.backgroundExtensionEffect()— 在安全區域邊緣鏡像/模糊(謹慎使用,每個畫面一個).safeAreaBar(edge:)— 附加與滾動效果整合的欄位視圖
完整的 ScrollPosition、分頁顯示、縮放/裁切衝突和 iOS 26 邊緣效果模式,請參閱 references/scrollview.md。
表單與控制器
Form
對於結構化的設定和輸入畫面,使用 Form。將相關控制器分組到 Section 區塊中。
Form {
Section("通知") {
Toggle("提及", isOn: $prefs.mentions)
Toggle("追蹤", isOn: $prefs.follows)
}
Section("外觀") {
Picker("主題", selection: $theme) {
ForEach(Theme.allCases, id: \.self) { Text($0.title).tag($0) }
}
Slider(value: $fontScale, in: 0.5...1.5, step: 0.1)
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
使用 @FocusState 來管理輸入密集型表單中的鍵盤焦點。僅在獨立呈現或位於 sheet 中時,才將其包裝在 NavigationStack 中。
控制器
| 控制器 | 用途 |
|---|---|
Toggle |
布林偏好設定 |
Picker |
離散選擇;2-4 個選項使用 .segmented |
Slider |
數值範圍,附帶可見的數值標籤 |
DatePicker |
日期/時間選擇 |
TextField |
文字輸入,搭配 .keyboardType、.textInputAutocapitalization |
直接將控制器綁定到 @State、@Binding 或 @AppStorage。在 Form 區段中將相關控制器分組。使用 .disabled(...) 來反映鎖定或繼承的設定。當有助於清晰度時,在切換開關內部使用 Label 來結合圖示和文字。
對於大型選項集,避免使用 .pickerStyle(.segmented);請改用選單或內聯樣式。不要隱藏滑桿的標籤;始終顯示上下文。
完整的表單範例請參閱 references/form.md。
可搜尋
使用 .searchable 新增原生搜尋 UI。使用 .searchScopes 實現多種模式,並使用 .task(id:) 實現防抖的非同步結果。
@MainActor
struct ExploreView: View {
@State private var searchQuery = ""
@State private var searchScope: SearchScope = .all
@State private var isSearching = false
@State private var results: [SearchResult] = []
var body: some View {
List {
if isSearching {
ProgressView()
} else {
ForEach(results) { result in
SearchRow(result: result)
}
}
}
.searchable(
text: $searchQuery,
placement: .navigationBarDrawer(displayMode: .always),
prompt: Text("搜尋")
)
.searchScopes($searchScope) {
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.title)
}
}
.task(id: searchQuery) {
await runSearch()
}
}
private func runSearch() async {
guard !searchQuery.isEmpty else {
results = []
return
}
isSearching = true
defer { isSearching = false }
try? await Task.sleep(for: .milliseconds(250))
results = await fetchResults(query: searchQuery, scope: searchScope)
}
}
當搜尋為空時顯示佔位符。對輸入進行防抖以避免過度擷取。將搜尋狀態保持在視圖本地。避免對空字串執行搜尋。
疊加與呈現
使用 .overlay(alignment:) 實現暫時性 UI(提示、橫幅),而不影響佈局。
struct AppRootView: View {
@State private var toast: Toast?
var body: some View {
content
.overlay(alignment: .top) {
if let toast {
ToastView(toast: toast)
.transition(.move(edge: .top).combined(with: .opacity))
.onAppear {
Task {
try? await Task.sleep(for: .seconds(2))
withAnimation { self.toast = nil }
}
}
}
}
}
}
對於暫時性 UI,優先使用疊加而非嵌入佈局堆疊。使用過場動畫和短暫的自動關閉計時器。將疊加對齊到清晰的邊緣(.top 或 .bottom)。除非明確需要,否則避免使用會阻止所有互動的疊加。不要堆疊多個疊加;使用佇列或取代當前的提示。
對於模態路由、sheet 調整大小和全螢幕呈現策略,請移交給 swiftui-navigation 技能。
常見錯誤
- 將
GeometryReader放在惰性容器內部會破壞惰性載入;當需要尺寸時,請使用.onGeometryChange。 - 陣列索引會產生不穩定的
ForEachID,並導致不正確的差異計算。 - 同軸巢狀滾動視圖會產生手勢衝突。
- 大型自訂或展開的
List列應屬於ScrollView+LazyVStack。 - 大型選項集應使用選單或內聯選擇器樣式,而非
.segmented。 - 除非有特定間距意圖,否則省略堆疊/網格的
spacing:以使用平台自適應預設值。 - 滾動驅動的顯示應使用一個標準化的進度值,而非並行的布林值或重複的拖曳手勢。
- 將每幀的滾動幾何保持在本機,並避免變更用於計算進度的幾何。
審查清單
- [ ] 對於大型或動態集合,使用
LazyVStack/LazyHStack - [ ] 所有
ForEach項目使用穩定的IdentifiableID(非陣列索引) - [ ] 惰性容器內部沒有
GeometryReader - [ ]
List樣式符合上下文(動態消息使用.plain,設定使用.insetGrouped) - [ ] 對於結構化的輸入畫面使用
Form(而非自訂堆疊) - [ ]
.searchable使用.task(id:)對輸入進行防抖 - [ ] 在資料來源支援下拉重新整理的地方加入
.refreshable - [ ] 疊加使用過場動畫和自動關閉計時器
- [ ] 在可點擊的列上使用
.contentShape(Rectangle()) - [ ]
@FocusState管理表單中的鍵盤焦點 - [ ] 除非需要特定值,否則省略堆疊/網格的
spacing: - [ ] 滾動驅動的顯示使用一個標準化的進度值,並將幾何更新保持在狹窄的子樹中
- [ ] 衝突的縮放/裁切互動會停用滾動,且離散的可見性效果不會驅動連續動畫
參考資料
- 網格模式:references/grids.md
- 列表和區段模式:references/list.md
- ScrollView 和惰性堆疊:references/scrollview.md
- 表單模式:references/form.md
- 架構和狀態管理:請參閱
swiftui-patterns技能 - 導航模式:請參閱
swiftui-navigation技能






