List là một View container trong SwiftUI để hiển thị dữ liệu dưới dạng danh sách cuộn dọc, tương tự như UITableView trong UIKit. Theo Apple Developer Documentation, 2024, List hỗ trợ các phần tĩnh và động, hành động vuốt, sắp xếp lại hàng và kéo để làm mới. Không giống như UITableView, List sử dụng API khai báo dựa trên SwiftUI và ForEach, tự động quản lý việc tái sử dụng ô và hiệu suất với số lượng hàng lớn.
Những điểm chính
List là một View hiển thị một chuỗi các mục trong một danh sách cuộn dọc. Nó được giới thiệu trong iOS 13 cùng với SwiftUI và là cách chính để hiển thị danh sách dữ liệu, thay thế UITableView từ UIKit. List tự động quản lý việc tái sử dụng ô, cuộn và hiệu suất.
List sử dụng tải chậm: các ô được tạo khi bạn cuộn, không phải tất cả cùng một lúc. Điều này làm nó khác biệt so với VStack với ForEach bên trong ScrollView, nơi tất cả các ô được tạo tại thời điểm render. List cũng cung cấp hỗ trợ tích hợp cho các hành động vuốt, kéo để làm mới, chỉnh sửa (xóa/di chuyển) và chọn hàng.
Theo Apple WWDC 2021 (Session 10072), List trong iOS 15+ đã nhận được những cải thiện hiệu suất đáng kể nhờ cơ chế diffing mới ở cấp độ collection. Điều này giúp List hiệu quả hơn khi cập nhật dữ liệu, đặc biệt cho danh sách có hàng trăm hàng.
Các nhà phát triển thường chọn giữa List và ScrollView với VStack để hiển thị một tập hợp các View. Sự khác biệt chính: List sử dụng tái sử dụng ô (giống UITableView), trong khi ScrollView + VStack tạo tất cả các View ngay lập tức. Đối với danh sách có kích thước cố định (tối đa 20 mục), sự khác biệt là không đáng kể. Đối với danh sách động có 50+ hàng, List được ưu tiên hơn về hiệu suất.
Danh sách tĩnh là danh sách có số lượng hàng cố định được chỉ định trực tiếp trong thân List. Nó được sử dụng cho menu, cài đặt và biểu mẫu với tập hợp các mục đã biết. Mỗi hàng được khai báo rõ ràng, không có vòng lặp hoặc ForEach.
// Danh sách tĩnh (cho menu và cài đặt)
List {
Text("Hồ sơ")
Text("Cài đặt")
Text("Giới thiệu")
}
// Danh sách động (cho dữ liệu)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
Danh sách động sử dụng bộ khởi tạo List(data:rowContent:) hoặc ForEach bên trong thân List. Cách tiếp cận đầu tiên thuận tiện khi mỗi hàng tương ứng với một mục dữ liệu. Cách thứ hai hữu ích khi có các phần hoặc mục bổ sung giữa dữ liệu.
Nhận dạng (Identifiable): Đối với danh sách động, các mục dữ liệu phải tuân theo giao thức Identifiable hoặc bạn phải chỉ định KeyPath đến một định danh duy nhất trong tuple data:id. SwiftUI sử dụng định danh để theo dõi các thay đổi: thêm, xóa và di chuyển hàng.
Section là một View để nhóm các hàng trong List với tiêu đề và footer tùy chọn. Section chấp nhận header và footer dưới dạng ViewBuilder, cho phép sử dụng không chỉ văn bản mà còn cả Views tùy chỉnh cho tiêu đề phần.
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("Tài khoản")) {
Text("Tên")
Text("Email")
}
Section(header: Text("Thông báo")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// Phần động với ForEach
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
Kiểu List: SwiftUI cung cấp một số kiểu tích hợp thông qua bộ điều chỉnh .listStyle(). .insetGrouped — tiêu chuẩn cho iOS Settings, .plain — tối giản, .inset — có thụt lề, .sidebar — cho Sidebar trên iPad.
Theo SwiftUI Cookbook (2024), Section với các phần động và ForEach bên trong là một mẫu tiêu chuẩn để nhóm dữ liệu trong các ứng dụng có cấu trúc phức tạp. Quy tắc chính: không lồng Section bên trong Section và không sử dụng bộ khởi tạo List(data:) cùng với Section — hãy sử dụng ForEach bên trong thân List.
.swipeActions(edge:allowsFullSwipe:content:) — bộ điều chỉnh cho iOS 15+ thêm hành động vuốt vào các hàng List. Cho phép hiển thị các nút khi vuốt sang trái (mặc định) hoặc phải, với các màu sắc và vai trò khác nhau (destructive, cancel).
struct TaskList: View {
@Binding var tasks: [Task]
var body: some View {
List {
ForEach($tasks) { $task in
Text(task.title)
.swipeActions(edge: .trailing) {
Button("Xóa", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "Hoàn tác" : "Xong") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// Tải dữ liệu bất đồng bộ
await loadTasks()
}
}
}
.refreshable — bộ điều chỉnh cho iOS 15+ thêm kéo để làm mới. Chấp nhận một closure bất đồng bộ được thực thi khi người dùng kéo danh sách xuống. SwiftUI tự động hiển thị chỉ báo tải. Sau khi hoạt động hoàn tất, chỉ báo sẽ bị ẩn.
.onDelete và .onMove — bộ điều chỉnh cho iOS 13+ thêm hỗ trợ xóa và sắp xếp lại hàng. Để sử dụng chúng, hãy bọc dữ liệu trong ForEach với Binding hoặc truyền closure qua .onDelete(perform:) trên List hoặc ForEach.
Hiệu suất List phụ thuộc vào số lượng hàng, độ phức tạp của mỗi ô và tần suất cập nhật dữ liệu. SwiftUI sử dụng tải chậm và tái sử dụng ô (tương tự UITableView.dequeueReusableCell), nhưng có thể cần tối ưu hóa bổ sung cho danh sách có 500+ hàng.
| Tối ưu hóa | Mô tả | Phiên bản iOS |
|---|---|---|
| Identifiable | ID duy nhất cho mỗi mục | iOS 13+ |
| EquatableView | Tránh vẽ lại khi dữ liệu bằng nhau | iOS 13+ |
| id(_:) | Buộc tạo lại View khi ID thay đổi | iOS 13+ |
| .equatable() | So sánh chặt chẽ theo Equatable | iOS 15+ |
| Diffable data | Diff tự động khi có thay đổi | iOS 15+ |
Vấn đề 1: Cập nhật thường xuyên. Nếu dữ liệu trong danh sách được cập nhật thường xuyên (ví dụ: mỗi giây), List có thể vẽ lại các ô hiển thị ở mỗi lần thay đổi trạng thái. Giải pháp: sử dụng cấu trúc (value types) cho dữ liệu — SwiftUI so sánh chúng theo giá trị và chỉ vẽ lại các hàng đã thay đổi.
Vấn đề 2: Ô nặng. Nếu mỗi hàng chứa hệ thống phân cấp View phức tạp, hình ảnh và hoạt ảnh, việc cuộn có thể bị chậm. Giải pháp: trích xuất các ô thành các View riêng biệt, sử dụng EquatableView để tránh vẽ lại không cần thiết. Theo SwiftUI Lab (2024), việc chia một hàng phức tạp thành các thành phần con giúp giảm thời gian render 30–50%.
Vấn đề 3: Số lượng hàng lớn. Với 1000+ hàng, List vẫn hoạt động hiệu quả nhờ tải chậm, nhưng tải ban đầu có thể chậm do tính toán bố cục. Giải pháp: chỉ sử dụng LazyVStack cho danh sách có các hàng đồng nhất khi không cần các tính năng của List (vuốt, phần). Đối với danh sách đầy đủ tính năng, List vẫn là lựa chọn tốt nhất.
Câu hỏi thường gặp
List là một View container để hiển thị danh sách dữ liệu có thể cuộn trong SwiftUI. Nó tương đương với UITableView trong UIKit với API khai báo. Hỗ trợ các phần, hành động vuốt, kéo để làm mới, chỉnh sửa và tùy chỉnh thông qua .listStyle().
List sử dụng tải chậm và tái sử dụng ô — các ô được tạo khi bạn cuộn. ScrollView + VStack tạo tất cả các Views cùng một lúc. Đối với danh sách có 50+ hàng, List được ưu tiên hơn. Đối với tập hợp nhỏ cố định (tối đa 20 mục), sự khác biệt là không đáng kể.
Sử dụng bộ điều chỉnh .refreshable (iOS 15+). Truyền một closure bất đồng bộ với logic cập nhật dữ liệu. SwiftUI tự động hiển thị chỉ báo tải và ẩn nó sau khi hoạt động bất đồng bộ hoàn tất.
Sử dụng Section View với tiêu đề và footer tùy chọn. Đặt các hàng danh sách bên trong Section. Đối với các phần động, sử dụng ForEach với groupedData. Kiểu danh sách được cấu hình qua .listStyle(.insetGrouped) để có giao diện giống iOS.
Sử dụng cấu trúc (value types) cho dữ liệu, trích xuất các ô phức tạp thành các View riêng biệt với EquatableView, tránh cập nhật trạng thái thường xuyên trong mỗi hàng. Đối với danh sách có 1000+ hàng, hãy cân nhắc LazyVStack nếu không cần các tính năng của List.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm