2.7 KiB
2.7 KiB
List and Section
Intent
Use List for feed-style content and settings-style rows where built-in row reuse, selection, and accessibility matter.
Core patterns
- Prefer
Listfor long, vertically scrolling content with repeated rows. - Use
Sectionheaders to group related rows. - Pair with
ScrollViewReaderwhen you need scroll-to-top or jump-to-id. - Use
.listStyle(.plain)for modern feed layouts. - Use
.listStyle(.grouped)for multi-section discovery/search pages where section grouping helps. - Apply
.scrollContentBackground(.hidden)+ a custom background when you need a themed surface. - Use
.listRowInsets(...)and.listRowSeparator(.hidden)to tune row spacing and separators. - Use
.environment(\\.defaultMinListRowHeight, ...)to control dense list layouts.
Example: feed list with scroll-to-top
@MainActor
struct TimelineListView: View {
@Environment(\.selectedTabScrollToTop) private var selectedTabScrollToTop
@State private var scrollToId: String?
var body: some View {
ScrollViewReader { proxy in
List {
ForEach(items) { item in
TimelineRow(item: item)
.id(item.id)
.listRowInsets(.init(top: 12, leading: 16, bottom: 6, trailing: 16))
.listRowSeparator(.hidden)
}
}
.listStyle(.plain)
.environment(\\.defaultMinListRowHeight, 1)
.onChange(of: scrollToId) { _, newValue in
if let newValue {
proxy.scrollTo(newValue, anchor: .top)
scrollToId = nil
}
}
.onChange(of: selectedTabScrollToTop) { _, newValue in
if newValue == 0 {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
}
}
}
}
Example: settings-style list
@MainActor
struct SettingsView: View {
var body: some View {
List {
Section("General") {
NavigationLink("Display") { DisplaySettingsView() }
NavigationLink("Haptics") { HapticsSettingsView() }
}
Section("Account") {
Button("Sign Out", role: .destructive) {}
}
}
.listStyle(.insetGrouped)
}
}
Design choices to keep
- Use
Listfor dynamic feeds, settings, and any UI where row semantics help. - Use stable IDs for rows to keep animations and scroll positioning reliable.
- Prefer
.contentShape(Rectangle())on rows that should be tappable end-to-end. - Use
.refreshablefor pull-to-refresh feeds when the data source supports it.
Pitfalls
- Avoid heavy custom layouts inside a
Listrow; useScrollView+LazyVStackinstead. - Be careful mixing
Listand nestedScrollView; it can cause gesture conflicts.