List — to kontenerowe View w SwiftUI do wyświetlania danych w formie pionowo przewijanej listy, odpowiednik UITableView w UIKit. Według Apple Developer Documentation, 2024, List obsługuje statyczne i dynamiczne sekcje, akcje przesuwania, przeciąganie wierszy i pull-to-refresh. W przeciwieństwie do UITableView, List używa deklaratywnego API opartego na SwiftUI i ForEach, automatycznie zarządzając ponownym wykorzystaniem komórek i wydajnością przy dużej liczbie wierszy.
Najważniejsze
List — to View wyświetlające sekwencję elementów w pionowo przewijanej liście. Pojawiło się w iOS 13 wraz ze SwiftUI i jest podstawowym sposobem wyświetlania list danych, zastępując UITableView z UIKit. List automatycznie zarządza ponownym wykorzystaniem komórek, przewijaniem i wydajnością.
List używa lazy-loading: komórki są tworzone w miarę przewijania, a nie wszystkie naraz. To odróżnia go od VStack z ForEach wewnątrz ScrollView, gdzie wszystkie komórki są tworzone podczas renderowania. List zapewnia również wbudowaną obsługę akcji przesuwania, pull-to-refresh, edycji (delete/move) i zaznaczania wierszy.
Według Apple WWDC 2021 (Session 10072), List w iOS 15+ otrzymał znaczące ulepszenia wydajności dzięki nowemu mechanizmowi diffing na poziomie kolekcji. To sprawiło, że List jest bardziej efektywny przy aktualizacji danych, szczególnie dla list z setkami wierszy.
Deweloperzy często wybierają między List a ScrollView z VStack do wyświetlania zestawu View. Kluczowa różnica: List używa ponownego wykorzystania komórek (jak UITableView), a ScrollView + VStack tworzy wszystkie View naraz. Dla list ze stałą liczbą elementów (do 20) różnica jest nieznaczna. Dla dynamicznych list z 50+ wierszami List jest preferowany ze względu na wydajność.
Statyczna List — to lista ze stałą liczbą wierszy, określonych bezpośrednio w ciele List. Używana do menu, ustawień i formularzy ze znanym zestawem elementów. Każdy wiersz jest deklarowany jawnie, bez pętli lub ForEach.
// Lista statyczna (dla menu i ustawień)
List {
Text("Profil")
Text("Ustawienia")
Text("O aplikacji")
}
// Lista dynamiczna (dla danych)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
Dynamiczna List używa inicjalizatora List(data:rowContent:) lub ForEach wewnątrz ciała List. Pierwsza opcja jest wygodna, gdy każdy wiersz odpowiada jednemu elementowi danych. Druga — gdy są sekcje lub dodatkowe elementy między danymi.
Identyfikacja (Identifiable): Dla dynamicznych list elementy danych muszą być zgodne z protokołem Identifiable lub w krotce data:id należy podać KeyPath do unikalnego identyfikatora. SwiftUI używa identyfikatorów do śledzenia zmian: dodawania, usuwania i przenoszenia wierszy.
Section — to View do grupowania wierszy w List z nagłówkiem i opcjonalnym footer. Section przyjmuje header i footer jako ViewBuilder, pozwalając używać nie tylko tekstu, ale także niestandardowych View dla nagłówków sekcji.
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("Konto")) {
Text("Nazwa")
Text("E-mail")
}
Section(header: Text("Powiadomienia")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// Sekcje dynamiczne z ForEach
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
Style List: SwiftUI udostępnia kilka wbudowanych stylów poprzez modyfikator .listStyle(). .insetGrouped — standardowy dla iOS Settings, .plain — minimalistyczny, .inset — z wcięciami, .sidebar — dla Sidebar na iPad.
Według SwiftUI Cookbook (2024), Section z dynamicznymi sekcjami i ForEach wewnątrz to standardowy wzorzec do grupowania danych w aplikacjach o złożonej strukturze. Kluczowa zasada: nie zagnieżdżaj Section wewnątrz Section i nie używaj inicjalizatora List(data:) razem z Section — używaj ForEach wewnątrz ciała List.
.swipeActions(edge:allowsFullSwipe:content:) — modyfikator dla iOS 15+, dodający akcje przesuwania do wierszy List. Pozwala wyświetlać przyciski przy przesunięciu w lewo (domyślnie) lub w prawo, z różnymi kolorami i rolami (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("Usuń", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "Cofnij" : "Gotowe") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// Asynchroniczne ładowanie danych
await loadTasks()
}
}
}
.refreshable — modyfikator dla iOS 15+, dodający pull-to-refresh. Przyjmuje asynchroniczne domknięcie (async), które jest wykonywane, gdy użytkownik przeciąga listę w dół. SwiftUI automatycznie wyświetla wskaźnik ładowania. Po zakończeniu operacji wskaźnik jest ukrywany.
.onDelete i .onMove — modyfikatory dla iOS 13+, dodające obsługę usuwania i przenoszenia wierszy. Aby ich użyć, owiń dane w ForEach z Binding lub przekaż domknięcia przez .onDelete(perform:) na List lub ForEach.
Wydajność List zależy od liczby wierszy, złożoności każdej komórki i częstotliwości aktualizacji danych. SwiftUI używa lazy-loading i ponownego wykorzystania komórek (analogicznie do UITableView.dequeueReusableCell), ale dodatkowe optymalizacje mogą być potrzebne dla list z 500+ wierszami.
| Optymalizacja | Opis | Wersja iOS |
|---|---|---|
| Identifiable | Unikalne ID dla każdego elementu | iOS 13+ |
| EquatableView | Unika przerysowania przy równych danych | iOS 13+ |
| id(_:) | Wymuszone odtworzenie View przy zmianie ID | iOS 13+ |
| .equatable() | Porównanie ściśle według Equatable | iOS 15+ |
| Diffable data | Automatyczny diff przy zmianach | iOS 15+ |
Problem 1: częste aktualizacje. Jeśli dane na liście są aktualizowane często (np. co sekundę), List może przerysowywać widoczne komórki przy każdej zmianie stanu. Rozwiązanie: używaj struktur (value types) dla danych — SwiftUI porównuje je według wartości i przerysowuje tylko zmienione wiersze.
Problem 2: ciężkie komórki. Jeśli każdy wiersz zawiera złożoną hierarchię View, obrazy i animacje, przewijanie może być wolne. Rozwiązanie: wydzielaj komórki do osobnych View, używaj EquatableView, aby zapobiec zbędnym przerysowaniom. Według SwiftUI Lab (2024), podział złożonego wiersza na podkomponenty zmniejsza czas renderowania o 30–50%.
Problem 3: duża liczba wierszy. Przy 1000+ wierszy List nadal działa wydajnie dzięki lazy-loading, ale początkowe ładowanie może zwolnić z powodu obliczania layoutu. Rozwiązanie: używaj LazyVStack tylko dla list z jednorodnymi wierszami, gdzie nie są potrzebne funkcje List (przesuwanie, sekcje). Dla w pełni funkcjonalnych list List pozostaje najlepszym wyborem.
Często zadawane pytania
List — kontenerowe View do wyświetlania przewijanej listy danych w SwiftUI. Odpowiednik UITableView w UIKit z deklaratywnym API. Obsługuje sekcje, akcje przesuwania, pull-to-refresh, edycję i dostosowywanie przez .listStyle().
List używa lazy-loading i ponownego wykorzystania komórek — komórki są tworzone w miarę przewijania. ScrollView + VStack tworzy wszystkie View naraz. Dla list z 50+ wierszami List jest preferowany. Dla stałych małych zestawów (do 20 elementów) różnica jest nieznaczna.
Użyj modyfikatora .refreshable (iOS 15+). Przekaż asynchroniczne domknięcie z logiką odświeżania danych. SwiftUI automatycznie wyświetla wskaźnik ładowania i ukrywa go po zakończeniu asynchronicznej operacji.
Użyj Section View z header i opcjonalnym footer. Wewnątrz Section umieść wiersze listy. Dla dynamicznych sekcji użyj ForEach z groupedData. Styl listy konfiguruje się przez .listStyle(.insetGrouped) dla wyglądu przypominającego iOS.
Używaj struktur (value types) dla danych, wydzielaj złożone komórki do osobnych View z EquatableView, unikaj częstych aktualizacji stanu w każdym wierszu. Dla list z 1000+ wierszami rozważ LazyVStack, jeśli nie są potrzebne funkcje List.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również