List — це контейнерне View у SwiftUI для відображення даних у вигляді вертикально прокручуваного списку, аналог UITableView в UIKit. Згідно з Apple Developer Documentation, 2024, List підтримує статичні та динамічні секції, свайп-дії, перетягування рядків і pull-to-refresh. На відміну від UITableView, List використовує декларативний API на основі SwiftUI та ForEach, автоматично керуючи перевикористанням комірок і продуктивністю при великій кількості рядків.
Головне
List — це View, що відображає послідовність елементів у вертикально прокручуваному списку. Воно з'явилося в iOS 13 разом зі SwiftUI і є основним способом відображення списків даних, замінюючи UITableView з UIKit. List автоматично керує перевикористанням комірок, скролом і продуктивністю.
List використовує lazy-loading: комірки створюються в міру прокручування, а не всі одразу. Це відрізняє його від VStack із ForEach всередині ScrollView, де всі комірки створюються під час рендерингу. List також надає вбудовану підтримку свайп-дій, pull-to-refresh, редагування (видалити/перемістити) та вибору рядків.
Згідно з Apple WWDC 2021 (Session 10072), List в iOS 15+ отримав суттєві покращення продуктивності завдяки новому механізму diffing на рівні колекцій. Це зробило List більш ефективним при оновленні даних, особливо для списків із сотнями рядків.
Розробники часто обирають між List і ScrollView з VStack для відображення набору View. Ключова відмінність: List використовує перевикористання комірок (як UITableView), а ScrollView + VStack створює всі View одразу. Для списків із фіксованою кількістю елементів (до 20) різниця незначна. Для динамічних списків із 50+ рядками List є кращим вибором з точки зору продуктивності.
Статичний List — це список із фіксованою кількістю рядків, заданих безпосередньо в тілі List. Використовується для меню, налаштувань і форм із відомим набором елементів. Кожен рядок оголошується явно, без циклів або ForEach.
// Статичний список (для меню та налаштувань)
List {
Text("Профіль")
Text("Налаштування")
Text("Про нас")
}
// Динамічний список (для даних)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
Динамічний List використовує ініціалізатор List(data:rowContent:) або ForEvaluate всередині тіла List. Перший варіант зручний, коли кожен рядок відповідає одному елементу даних. Другий — коли є секції або додаткові елементи між даними.
Ідентифікація (Identifiable): Для динамічних списків елементи даних повинні відповідати протоколу Identifiable або в кортежі data:id потрібно вказати KeyPath до унікального ідентифікатора. SwiftUI використовує ідентифікатори для відстеження змін: додавання, видалення та переміщення рядків.
Section — це View для групування рядків у List із заголовком та опціональним footer. Section приймає header і footer як ViewBuilder, дозволяючи використовувати не лише текст, а й кастомні View для заголовків секцій.
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("Акаунт")) {
Text("Ім'я")
Text("Електронна пошта")
}
Section(header: Text("Сповіщення")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// Динамічні секції з ForEach
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
Стилі List: SwiftUI надає кілька вбудованих стилів через модифікатор .listStyle(). .insetGrouped — стандартний для iOS Settings, .plain — мінімалістичний, .inset — з відступами, .sidebar — для Sidebar на iPad.
Згідно з SwiftUI Cookbook (2024), Section з динамічними секціями та ForEach всередині — стандартний патерн для групування даних у додатках зі складною структурою. Ключове правило: не вкладайте Section всередину Section і не використовуйте ініціалізатор List(data:) разом із Section — використовуйте ForEach всередині тіла List.
.swipeActions(edge:allowsFullSwipe:content:) — модифікатор для iOS 15+, що додає свайп-дії до рядків List. Дозволяє відображати кнопки при свайпі вліво (за замовчуванням) або вправо, з різними кольорами та ролями (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("Видалити", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "Скасувати" : "Готово") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// Асинхронне завантаження даних
await loadTasks()
}
}
}
.refreshable — модифікатор для iOS 15+, що додає pull-to-refresh. Приймає асинхронне замикання (async), яке виконується, коли користувач тягне список униз. SwiftUI автоматично відображає індикатор завантаження. Після завершення операції індикатор ховається.
.onDelete та .onMove — модифікатори для iOS 13+, що додають підтримку видалення та перестановки рядків. Для їх використання оберніть дані в ForEach із Binding або передайте замикання через .onDelete(perform:) на List або ForEach.
Продуктивність List залежить від кількості рядків, складності кожної комірки та частоти оновлень даних. SwiftUI використовує lazy-loading і перевикористання комірок (аналогічно UITableView.dequeueReusableCell), але додаткові оптимізації можуть знадобитися для списків із 500+ рядками.
| Оптимізація | Опис | Версія iOS |
|---|---|---|
| Identifiable | Унікальні ID для кожного елемента | iOS 13+ |
| EquatableView | Запобігає перемальовуванню при однакових даних | iOS 13+ |
| id(_:) | Примусове перестворення View при зміні ID | iOS 13+ |
| .equatable() | Порівняння строго за Equatable | iOS 15+ |
| Diffable data | Автоматичний diff при змінах | iOS 15+ |
Проблема 1: часті оновлення. Якщо дані в списку оновлюються часто (наприклад, кожну секунду), List може перемальовувати видимі комірки при кожній зміні стану. Рішення: використовуйте структури (value types) для даних — SwiftUI порівнює їх за значенням і перемальовує лише змінені рядки.
Проблема 2: важкі комірки. Якщо кожен рядок містить складну ієрархію View, зображення та анімації, скрол може гальмувати. Рішення: виносьте комірки в окремі View, використовуйте EquatableView для запобігання зайвим перемальовуванням. Згідно з SwiftUI Lab (2024), розбиття складного рядка на підкомпоненти знижує час рендерингу на 30–50%.
Проблема 3: велика кількість рядків. При 1000+ рядках List все ще працює ефективно завдяки lazy-loading, але початкове завантаження може сповільнитися через обчислення layout. Рішення: використовуйте LazyVStack лише для списків з однотипними рядками, де не потрібні функції List (свайпи, секції). Для повнофункціональних списків List залишається найкращим вибором.
Часті запитання
List — контейнерне View для відображення прокручуваного списку даних у SwiftUI. Аналог UITableView у UIKit з декларативним API. Підтримує секції, свайп-дії, pull-to-refresh, редагування та кастомізацію через .listStyle().
List використовує lazy-loading і перевикористання комірок — комірки створюються в міру прокручування. ScrollView + VStack створює всі View одразу. Для списків із 50+ рядками List кращий. Для фіксованих невеликих наборів (до 20 елементів) різниця незначна.
Використовуйте модифікатор .refreshable (iOS 15+). Передайте асинхронне замикання з логікою оновлення даних. SwiftUI автоматично відображає індикатор завантаження та ховає його після завершення асинхронної операції.
Використовуйте Section View із заголовком та опціональним footer. Всередині Section розміщуйте рядки списку. Для динамічних секцій використовуйте ForEach із groupedData. Стиль списку налаштовується через .listStyle(.insetGrouped) для iOS-подібного вигляду.
Використовуйте структури (value types) для даних, виносьте складні комірки в окремі View з EquatableView, уникайте частих оновлень стану в кожному рядку. Для списків із 1000+ рядками розгляньте LazyVStack, якщо не потрібні функції List.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також