List with Static Content
The simplest way to show a list is to put views directly inside a List. Each child becomes a row. List handles scrolling, selection, and platform-specific styling automatically.
List {
Text("Milk")
Text("Eggs")
Text("Bread")
Text("Butter")
}
Try it Yourself ->
ForEach for Dynamic Data
Static lists are fine for simple cases, but real apps display data from arrays and models. ForEach loops over your data and creates a view for each item. It's the workhorse behind dynamic lists.
let groceries = ["Milk", "Eggs", "Bread", "Butter", "Cheese"]
struct DynamicListView: View {
var body: some View {
List {
ForEach(groceries, id: \.self) { item in
Text(item)
}
}
}
}
Try it Yourself ->
Identifiable Protocol
ForEach needs a way to uniquely identify each item. If your data conforms to Identifiable, it just works. Each item gets an id property and Swift uses it to track rows efficiently.
struct Task: Identifiable {
let id = UUID()
var title: String
var isComplete: Bool
}
struct TaskListView: View {
let tasks = [
Task(title: "Buy groceries", isComplete: false),
Task(title: "Clean house", isComplete: true),
Task(title: "Write code", isComplete: false)
]
var body: some View {
List(tasks) { task in
HStack {
Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
Text(task.title)
}
}
}
}
Try it Yourself ->
Swipe Actions and Refreshable
Lists can respond to swipes and pull-to-refresh. Add .swipeActions for context-sensitive buttons, and .refreshable to load fresh data when the user pulls down.
List(items) { item in
Text(item.name)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
delete(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading) {
Button {
toggleComplete(item)
} label: {
Label("Complete", systemImage: "checkmark")
}
.tint(.green)
}
}
.refreshable {
await loadItems()
}
Try it Yourself ->
List Styles
Change how a list looks with the .listStyle modifier. Choose from inset, grouped, plain, or sidebar styles depending on your platform and design.
List {
Section("Favorites") {
Text("Coffee")
Text("Tea")
}
Section("Recent") {
Text("Water")
Text("Juice")
}
}
.listStyle(.insetGrouped)
Try it Yourself ->
NavigationStack and NavigationLink
Wrap your list in a NavigationStack and use NavigationLink to let users tap into detail views. The navigation stack manages the push/pop history for you.
struct Item: Identifiable {
let id = UUID()
let name: String
let detail: String
}
struct ItemListView: View {
let items = [
Item(name: "Swift", detail: "A powerful programming language."),
Item(name: "SwiftUI", detail: "Declarative UI framework."),
Item(name: "Xcode", detail: "Apple's IDE.")
]
var body: some View {
NavigationStack {
List(items) { item in
NavigationLink(item.name) {
VStack(alignment: .leading, spacing: 8) {
Text(item.name)
.font(.title)
Text(item.detail)
.foregroundColor(.secondary)
}
.padding()
}
}
.navigationTitle("Topics")
}
}
}
Try it Yourself ->