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, редактирования (delete/move) и выбора строк.
По данным 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.
// Static list (for menus and settings)
List {
Text("Profile")
Text("Settings")
Text("About")
}
// Dynamic list (for data)
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:) или ForEach внутри тела 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("Account")) {
Text("Name")
Text("Email")
}
Section(header: Text("Notifications")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// Dynamic sections with 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. Позволяет отображать кнопки при свайпе влево (default) или вправо, с разными цветами и ролями (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("Delete", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "Undo" : "Done") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// Async data loading
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 с header и опциональным footer. Внутри Section помещайте строки списка. Для динамических секций используйте ForEach с groupedData. Стиль списка настраивается через .listStyle(.insetGrouped) для iOS-подобного вида.
Используйте структуры (value types) для данных, выносите сложные ячейки в отдельные View с EquatableView, избегайте частых обновлений состояния в каждой строке. Для списков с 1000+ строк рассмотрите LazyVStack, если не нужны функции List.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также