gh-Dimillian-Skills/swiftui-ui-patterns/references/list.md
Thomas Ricouard 70a15d08db Add SwiftUI UI patterns skill and references
Introduces the 'swiftui-ui-patterns' skill to docs/skills.json, providing best practices and example-driven guidance for building SwiftUI views and components. Adds SKILL.md and a comprehensive set of reference files covering TabView, NavigationStack, sheets, forms, controls, grids, overlays, haptics, focus handling, media, matched transitions, split views, and more.
2026-01-04 18:26:56 +01:00

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 List for long, vertically scrolling content with repeated rows.
  • Use Section headers to group related rows.
  • Pair with ScrollViewReader when 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 List for 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 .refreshable for pull-to-refresh feeds when the data source supports it.

Pitfalls

  • Avoid heavy custom layouts inside a List row; use ScrollView + LazyVStack instead.
  • Be careful mixing List and nested ScrollView; it can cause gesture conflicts.