Lists, ForEach & Identifiable
The List view
List is SwiftUI’s scrolling, table-style container. It’s efficient (it reuses rows like UIKit’s table view) and gives you platform features like swipe-to-delete and sections almost for free.
struct FruitList: View {
let fruits = ["Apple", "Mango", "Kiwi"]
var body: some View {
List(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
Why identity matters: Identifiable
SwiftUI needs to tell items apart so it can update, insert, delete and animate them correctly. For simple values you pass id: \.self, but for your own data types it’s cleaner to make them Identifiable by giving them an id.
struct User: Identifiable {
let id = UUID() // a unique identity
let name: String
}
struct UsersView: View {
let users = [User(name: "Anand"), User(name: "Priya")]
var body: some View {
List(users) { user in // no id: needed — it's Identifiable
Text(user.name)
}
}
}
If identity is wrong or unstable, SwiftUI can animate the wrong rows or lose state — so always give list data a stable identity.
ForEach inside other containers
List is one use of ForEach, but you can use ForEach inside any container (a VStack, a LazyVStack) to repeat views over data.
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(users) { user in
Text(user.name).padding(.vertical, 4)
}
}
}
LazyVStack only builds visible rows (good for very long lists), whereas a plain VStack builds them all.
Sections and swipe actions
List {
Section("Team") {
ForEach(users) { user in Text(user.name) }
.onDelete { indexSet in /* remove items */ }
}
}
Common mistakes
- Using array index as identity — it shifts when items move, breaking animations and state. Use a stable
id. - Using a plain
VStackfor hundreds of rows — useListorLazyVStack. - Forgetting
Identifiableand litteringid: \.selfeverywhere.
Summary: UseList(orForEachin aLazyVStack) to show collections. Give your data a stable identity viaIdentifiableso SwiftUI updates and animates the right rows.