Содержание статьи

List – это основной компонент SwiftUI для отображения прокручиваемых наборов данных, который автоматически управляет созданием, обновлением и повторным использованием строк. Он применяется в экранах настроек, каталогах, лентах данных и любых интерфейсах, где элементы формируются на основе коллекций. В отличие от ручной верстки через ScrollView, List сразу предоставляет системное поведение строк, жестов и обновлений интерфейса.
Ключевая особенность List заключается в тесной связи с моделью данных. Каждый элемент списка должен быть однозначно идентифицирован, иначе SwiftUI не сможет корректно отслеживать изменения состояния. Для этого используются протокол Identifiable или явное указание ключа id. Неправильная идентификация приводит к ошибкам анимации, потере состояния ячеек и некорректной перерисовке.
List автоматически подстраивается под платформу: на iOS он повторяет поведение UITableView, а на macOS – NSTableView, сохраняя нативный внешний вид. Разработчик управляет содержимым декларативно, описывая, что должно отображаться, а не как это отрисовать. Это снижает количество шаблонного кода и упрощает поддержку экранов с динамическими данными.
Важно понимать ограничения List на раннем этапе. Он не подходит для сложных кастомных компоновок строк и требует аккуратной работы с состоянием и источниками данных. Осознанное использование List позволяет избежать проблем с обновлением интерфейса и выбрать корректную архитектуру экрана уже на стадии проектирования.
Назначение List в SwiftUI и типичные сценарии использования
List в SwiftUI предназначен для отображения упорядоченных коллекций данных с поддержкой прокрутки, обновлений состояния и системных жестов. Он берет на себя управление жизненным циклом строк, синхронизируя интерфейс с источником данных без ручной работы с индексами и перерисовкой.
Компонент используется в ситуациях, где данные представлены в виде последовательных элементов и могут изменяться во времени. List автоматически реагирует на вставку, удаление и перестановку элементов, если коллекция связана с состоянием или наблюдаемой моделью.
- экраны настроек с группировкой параметров через Section
- каталоги товаров, статей или сообщений на основе массивов и результатов запросов
- списки задач, заметок и событий с возможностью редактирования
- результаты поиска с динамическим обновлением содержимого
- навигационные меню, интегрированные с NavigationStack
List подходит для работы с большими наборами данных за счет ленивого создания строк. Элементы создаются только в момент отображения, что снижает нагрузку на интерфейс при прокрутке длинных списков.
Следует выбирать List, если требуется:
- стандартное поведение строк, соответствующее платформе
- поддержка системных жестов удаления и перемещения
- автоматическая синхронизация с моделью данных
Для экранов с нестандартной компоновкой элементов или сложной версткой внутри строки лучше рассмотреть альтернативные контейнеры. List ориентирован на структурированные данные, где каждая строка представляет один логический объект модели.
Как создать базовый List с массивом данных
Пример с массивом строк:
«`swift
let items = [«Первый», «Второй», «Третий»]
List(items, id: .self) { item in
Text(item)
}
swiftЗдесь каждый элемент массива идентифицируется с помощью \.self. Этот подход работает для типов данных, которые могут быть уникально идентифицированы, как например строки или числа.
Если данные представляют собой сложные объекты, необходимо использовать идентификатор для каждой модели. В этом случае удобно реализовать протокол Identifiable для пользовательских типов. Это упростит работу с List и избавит от необходимости явно указывать идентификаторы:
«`swift
struct Task: Identifiable {
var id = UUID()
var title: String
}
let tasks = [
Task(title: «Купить хлеб»),
Task(title: «Написать статью»)
]
List(tasks) { task in
Text(task.title)
}
Теперь каждый элемент автоматически получает уникальный идентификатор благодаря свойству id. Использование Identifiable позволяет SwiftUI отслеживать изменения и обновлять только те строки, которые были изменены.
Когда список связан с изменяемыми данными (например, через @State или @ObservedObject), любые манипуляции с коллекцией (добавление, удаление, перемещение) автоматически отразятся в интерфейсе. Это позволяет создавать динамичные списки с минимальными усилиями.
Основное правило при создании базового List – правильно настроить идентификацию элементов, чтобы SwiftUI мог корректно управлять обновлениями и анимациями, сохраняя стабильность интерфейса.
Отображение пользовательских ячеек внутри List

Для создания кастомных ячеек внутри List в SwiftUI нужно настроить представление каждой строки с использованием компонентов, которые соответствуют нуждам интерфейса. По умолчанию, List отображает данные с помощью стандартных представлений, таких как Text, но для более сложных элементов используется HStack, VStack и другие контейнеры.
Чтобы создать пользовательскую ячейку, необходимо определить, как именно будет выглядеть контент каждой строки. Например, если нужно отобразить картинку и текст в одной строке, можно использовать следующий код:
«`swift
struct User: Identifiable {
var id = UUID()
var name: String
var avatar: String
}
let users = [
User(name: «Алексей», avatar: «alexey.jpg»),
User(name: «Ольга», avatar: «olga.jpg»)
]
List(users) { user in
HStack {
Image(user.avatar)
.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.clipShape(Circle())
Text(user.name)
.font(.headline)
.padding(.leading, 10)
}
}
cssCopy codeВ этом примере используется HStack, чтобы разместить изображение и текст рядом. Для изображения применяется модификатор clipShape, чтобы сделать его круглым. Такой подход подходит для отображения пользовательских аватаров или картинок.
Для более сложных ячеек, например с несколькими элементами, можно комбинировать различные контейнеры. Например, если нужно добавить описание и кнопку:
«`swift
List(users) { user in
VStack(alignment: .leading) {
HStack {
Image(user.avatar)
.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.clipShape(Circle())
Text(user.name)
.font(.headline)
}
Text(«Краткое описание пользователя»)
.font(.subhea
Как работает идентификация элементов через Identifiable и id
В SwiftUI для корректного отслеживания и обновления данных внутри List необходимо обеспечить уникальную идентификацию каждого элемента. Это достигается с помощью протокола Identifiable или явного указания свойства id.
Когда коллекция элементов передается в List, SwiftUI должен понять, как однозначно идентифицировать каждый объект, чтобы правильно отслеживать изменения данных, а также производить обновление интерфейса, добавление или удаление строк. Если элементы имеют уникальный идентификатор, SwiftUI может минимизировать перерасход ресурсов при изменении состояния и поддерживать стабильную анимацию.
При реализации протокола Identifiable структура или класс автоматически получают уникальный идентификатор через свойство id, которое является обязательным для объектов этого типа. Например:
«`swift
struct Task: Identifiable {
var id = UUID()
var title: String
}
let tasks = [
Task(title: «Купить хлеб»),
Task(title: «Прочитать книгу»)
]
List(tasks) { task in
Text(task.title)
}
«>
Здесь каждому элементу автоматически присваивается уникальный UUID в качестве идентификатора. Это позволяет SwiftUI отслеживать изменения в коллекции и корректно обновлять ячейки при добавлении или удалении элементов.
Если объект уже имеет свойство, которое может служить уникальным идентификатором (например, база данных может использовать числовые идентификаторы), его можно использовать в качестве id. Например:
«`swift
struct User: Identifiable {
var id: Int
var name: String
}
let users = [
User(id: 1, name: «Иван»),
User(id: 2, name: «Мария»)
]
List(users) { user in
Text(user.name)
}
«>
В этом примере свойство id используется напрямую, и SwiftUI будет использовать его для идентификации элементов списка.
При работе с пользовательскими данными важно использовать уникальные идентификаторы, чтобы избежать ошибок в обновлениях интерфейса. Неправильная идентификация может привести к неверной синхронизации данных, что особенно критично для анимаций и удаления элементов списка.
Если коллекция не реализует Identifiable, можно явно передать параметр id в инициализатор List. Например, для массива с простыми типами данных:
«`swift
let names = [«Иван», «Мария», «Алексей»]
List(names, id: .self) { name in
Text(name)
}
«>
Здесь используется \.self для идентификации элементов, что работает только для типов данных, которые могут быть уникально определены (например, строки или числа). Однако для сложных моделей рекомендуется использовать Identifiable.
В итоге, правильная настройка идентификации элементов позволяет SwiftUI эффективно управлять данными и минимизировать излишние перерисовки, что повышает производительность и стабильность приложения.
Обработка нажатий и жестов внутри List
В SwiftUI для обработки нажатий и жестов в List используется система событий, которая позволяет взаимодействовать с элементами списка. Для этого можно применить модификаторы, такие как onTapGesture, onLongPressGesture, а также встроенные действия, например, обработку свайпов для удаления строк.
Простейший способ – это использование модификатора onTapGesture, который позволяет реагировать на одиночные касания элементов в списке. Например, чтобы обрабатывать нажатие на строку списка:
«`swift
List(items) { item in
Text(item)
.onTapGesture {
print(«Нажата строка: \(item)»)
}
}
«>
Этот код добавляет обработку нажатий для каждой строки, и при нажатии на элемент консоль выведет его название. Такой подход удобно использовать для простых взаимодействий, например, для навигации или изменения состояния.
Для более сложных жестов, например, долгого нажатия, можно использовать onLongPressGesture. Это подходит для добавления дополнительных действий, таких как отображение меню или вызов контекстного меню:
«`swift
List(items) { item in
Text(item)
.onLongPressGesture {
print(«Долгое нажатие на строку: \(item)»)
}
}
«>
Еще один важный аспект – обработка свайпов для удаления или перемещения строк. Это реализуется с помощью модификатора onDelete для удаления элементов и onMove для изменения их порядка:
«`swift
List {
ForEach(items, id: \.self) { item in
Text(item)
}
.onDelete { indices in
items.remove(atOffsets: indices)
}
.onMove { indices, newOffset in
items.move(fromOffsets: indices, toOffset: newOffset)
}
}
«>
Здесь используется onDelete для удаления элементов через свайп и onMove для изменения их порядка. Эти жесты позволяют добавлять нативное поведение, привычное пользователям на мобильных устройствах.
Для использования жестов в сложных интерфейсах, например, с анимациями или множественными действиями на одном элементе, можно комбинировать несколько модификаторов. Например, для обработки свайпа с задержкой:
«`swift
List(items) { item in
Text(item)
.onLongPressGesture(minimumDuration: 1) {
print(«Долгое нажатие на элемент с задержкой: \(item)»)
}
.gesture(
DragGesture(minimumDistance: 20)
.onChanged { _ in
print(«Свайп влево на элементе: \(item)»)
}
)
}
«>
В этом примере комбинируется жест долгого нажатия и свайп. Такие подходы полезны, если нужно обработать сложные пользовательские взаимодействия.
Обработка нажатий и жестов в List позволяет создавать интерактивные и отзывчивые интерфейсы, улучшая пользовательский опыт и добавляя функциональность, соответствующую нативному поведению системы. Главное – правильно настроить жесты для каждой строки, чтобы не нарушать взаимодействие с остальными элементами.
Настройка разделов и заголовков Section в List

В SwiftUI можно организовать данные внутри List, разделив их на секции с помощью компонента Section. Это полезно, когда нужно логически группировать элементы, например, по категориям или датам. Секцию можно снабдить заголовком, который будет отображаться в соответствующем месте списка.
Чтобы создать секции внутри List, достаточно использовать Section, указав заголовок для каждой группы. Например, если у нас есть список пользователей, разделенный по первым буквам их имени:
«`swift
let users = [
«Alice», «Adam», «Bob», «Charlie», «Clara»
]
let groupedUsers = Dictionary(grouping: users) { $0.prefix(1) }
List {
ForEach(groupedUsers.keys.sorted(), id: .self) { key in
Section(header: Text(«Начинается на (key)»)) {
ForEach(groupedUsers[key]!, id: .self) { user in
Text(user)
}
}
}
}
«>
Здесь данные сгруппированы по первой букве имени, и каждая секция имеет свой заголовок, который соответствует этой букве. Внутри каждой секции используется обычный ForEach для отображения элементов.
Важно помнить, что для правильного отображения заголовков, секции должны быть внутри общего List, и каждый Section должен содержать хотя бы один элемент. Заголовок в секции задается через параметр header, и он может быть любым видом представления, например, Text или более сложным компонентом.
Можно также настроить футеры для секций с помощью параметра footer, что удобно для отображения дополнительной информации по каждой группе:
«`swift
List {
Section(header: Text(«Пользователи»)) {
ForEach(users, id: \.self) { user in
Text(user)
}
}
.footer {
Text(«Всего пользователей: \(users.count)»)
}
}
«>
В этом примере добавлен футер, который отображает общее количество пользователей после всех элементов секции. Это позволяет сделать информацию в списке более интерактивной и информативной.
Для динамичных данных, где разделы могут изменяться, важно использовать привязку данных через @State или @ObservedObject, чтобы изменения в модели данных автоматически обновляли отображение секций. Например, при добавлении или удалении элементов из группы, соответствующие изменения будут отображены в реальном времени.
Использование секций и заголовков позволяет улучшить восприятие данных в списке, делая интерфейс более структурированным и удобным для пользователя.
Удаление, перемещение и редактирование строк List
В SwiftUI можно легко реализовать операции удаления, перемещения и редактирования строк в List, что делает его удобным инструментом для работы с динамическими данными. Для каждой из этих операций в List есть встроенные модификаторы и методы.
Удаление строк
Для удаления строк используется модификатор onDelete, который позволяет добавить возможность удаления элементов через свайп. Для этого List должен быть связан с изменяемыми данными, например, через @State. Вот пример использования:
«`swift
@State private var items = [«Apple», «Banana», «Orange»]
List {
ForEach(items, id: .self) { item in
Text(item)
}
.onDelete { indices in
items.remove(atOffsets: indices)
}
}
«>
В этом примере строки можно удалить с помощью свайпа. Метод remove(atOffsets:) удаляет элементы из массива, а List автоматически обновляет интерфейс.
Перемещение строк
Перемещение строк в List реализуется с помощью модификатора onMove. Этот модификатор позволяет пользователям перетаскивать строки в пределах списка, чтобы изменить их порядок. Для этого также используется ForEach, а массив должен быть изменяемым:
«`swift
@State private var items = [«Apple», «Banana», «Orange»]
List {
ForEach(items, id: .self) { item in
Text(item)
}
.onMove { indices, newOffset in
items.move(fromOffsets: indices, toOffset: newOffset)
}
}
«>
Метод move(fromOffsets:toOffset:) меняет порядок элементов в массиве при перетаскивании строк. Важно, чтобы массив был изменяемым, чтобы изменения автоматически отразились в интерфейсе.
Редактирование строк
Для редактирования строк можно использовать различные подходы в зависимости от контекста. Одним из способов является использование TextField внутри строк для редактирования текста. Для этого используется привязка данных через @State:
«`swift
@State private var items = [«Apple», «Banana», «Orange»]
@State private var editingItem: String?
List {
ForEach(items, id: .self) { item in
HStack {
if editingItem == item {
TextField(«Edit», text: $editingItem)
.textFieldStyle(RoundedBorderTextFieldStyle())
} else {
Text(item)
}
Button(«Edit») {
editingItem = item
}
}
}
}
«>
Этот пример позволяет редактировать строки, отображая TextField вместо обычного текста, когда пользователь нажимает кнопку «Edit». Привязка данных через @State позволяет обновлять модель данных в реальном времени.
Комбинирование операций
Удаление, перемещение и редактирование могут быть использованы совместно. Например, можно реализовать интерфейс, который позволяет пользователю перемещать строки, редактировать их или удалять, комбинируя все три операции в одном списке:
«`swift
@State private var items = [«Apple», «Banana», «Orange»]
List {
ForEach(items, id: .self) { item in
Text(item)
}
.onDelete { indices in
items.remove(atOffsets: indices)
}
.onMove { indices, newOffset in
items.move(fromOffsets: indices, toOffset: newOffset)
}
}
.navigationBarItems(trailing: EditButton())
«>
Здесь добавлена кнопка редактирования EditButton, которая позволяет переключать режим редактирования для списка. В этом режиме можно удалять и перемещать строки.
Для эффективной работы с динамическими данными в List важно, чтобы коллекция данных была изменяемой (например, с использованием @State, @ObservedObject или @EnvironmentObject). SwiftUI автоматически синхронизирует изменения в данных с интерфейсом, что значительно упрощает разработку.
Отличия List от ScrollView и LazyVStack на практике
В SwiftUI компоненты List, ScrollView и LazyVStack имеют схожие функции для создания прокручиваемых интерфейсов, но они различаются по производительности, способу работы с данными и функционалу. Каждое из этих решений имеет свои особенности и применимо в разных ситуациях.
List

Основная цель List – это отображение данных в виде списка с поддержкой прокрутки, а также встроенными возможностями для удаления, перемещения и редактирования элементов. В отличие от обычных прокручиваемых контейнеров, таких как ScrollView, List автоматически отслеживает изменения в данных и обновляет UI. Это делает List оптимальным решением для работы с динамическими списками, где элементы могут изменяться во времени.
Когда List используется с динамическими данными (например, через привязки с @State или @ObservedObject), SwiftUI автоматически управляет состоянием элементов и минимизирует перерисовку, что повышает производительность при больших объемах данных.
ScrollView
ScrollView позволяет прокручивать любые компоненты внутри, включая неструктурированные данные, изображения и прочее. Однако ScrollView не имеет встроенных механизмов для работы с данными, таких как идентификация элементов или перерисовка при изменениях. Он используется в тех случаях, когда нужно отображать прокручиваемое содержимое без необходимости работать с отдельными строками или ячейками.
В отличие от List, ScrollView не поддерживает такие функции, как удаление или перемещение элементов. Он просто выполняет роль контейнера для прокручиваемого контента, не отслеживая индексы или изменения в данных.
LazyVStack

LazyVStack – это вертикальный стек, который лениво загружает элементы, только когда они появляются на экране. Это решение идеально подходит для случаев, когда необходимо отобразить большой объем данных, но производительность важна. В отличие от List, LazyVStack не предоставляет таких встроенных функций, как удаление или перемещение строк, но при этом позволяет контролировать, когда и какие элементы загружаются, что снижает нагрузку на память и ускоряет прокрутку.
LazyVStack часто используется в сочетании с ScrollView, чтобы создать прокручиваемый контейнер с ленивой загрузкой элементов. Это позволяет комбинировать преимущества обоих компонентов – прокрутку и экономию ресурсов при отображении большого списка данных.
Сравнение на практике
Каждый из этих компонентов подходит для разных задач, и их выбор зависит от требований к функциональности и производительности приложения. Например, если нужно отобразить список, где возможны изменения данных (добавление, удаление, перемещение), то предпочтительнее использовать List. Для простых прокручиваемых интерфейсов с неподвижным контентом лучше подойдет ScrollView. Когда же важна оптимизация загрузки данных, например, для длинных списков, лучше использовать LazyVStack в сочетании с ScrollView.
Пример использования:
| Компонент | Преимущества | Когда использовать |
|---|---|---|
| List | Поддержка удаления, перемещения, редактирования, автоматическое обновление | Когда нужно работать с динамическими данными и поддерживать стандартные взаимодействия |
| ScrollView | Гибкость, поддержка любых элементов | Когда нужно создать прокручиваемое пространство для произвольных данных |
| LazyVStack | Ленивая загрузка элементов, экономия памяти | Когда нужно отображать большое количество данных с высокой производительностью |
Таким образом, выбор между List, ScrollView и LazyVStack зависит от структуры данных и требуемой функциональности. List подходит для динамических списков с интерактивностью, ScrollView – для простых прокручиваемых представлений, а LazyVStack – для оптимизации производительности при работе с большими наборами данных.
Вопрос-ответ:
Что такое List в SwiftUI и зачем он используется?
List в SwiftUI — это компонент, который позволяет создавать прокручиваемые списки элементов на экране. Он идеально подходит для отображения данных в виде строк, ячеек или секций. В отличие от обычных контейнеров, таких как ScrollView, List предоставляет функциональность для работы с динамическими данными: элементы можно удалять, перемещать, редактировать, а сама коллекция обновляется автоматически при изменениях. Это особенно полезно для приложений, где требуется отображение большого объема данных с минимальными усилиями по обновлению интерфейса.
Как List в SwiftUI обновляет интерфейс при изменении данных?
SwiftUI использует систему связывания данных для автоматического обновления интерфейса. Когда данные в модели изменяются (например, с помощью @State или @ObservedObject), List автоматически реагирует на эти изменения, не требуя от разработчика вручную обновлять UI. Например, при удалении элемента из списка, List сразу же отобразит актуальное состояние, убрав удаленную строку. Этот подход значительно упрощает работу с динамическими списками и помогает поддерживать синхронизацию между данными и интерфейсом.
Можно ли настроить кастомные ячейки в List и как это сделать?
Да, можно. Для создания кастомных ячеек в List в SwiftUI нужно использовать различные контейнеры, такие как HStack, VStack, а также добавить компоненты, такие как Text, Image и Button. Это позволяет настроить внешний вид ячеек в соответствии с потребностями приложения. Например, если нужно отобразить текст и изображение в одной строке, можно использовать следующий код:
Как работает идентификация элементов в List через Identifiable и id?
Для правильной работы с данными в List SwiftUI требует уникальной идентификации каждого элемента. Это можно реализовать с помощью протокола Identifiable, который заставляет тип данных предоставлять уникальное значение для каждого элемента через свойство id. Например:
Что выбрать для прокручиваемого списка: List или ScrollView?
Выбор между List и ScrollView зависит от задачи. Если вам нужно отображать список с динамическими данными, где элементы могут изменяться, перемещаться или удаляться, то предпочтительнее использовать List, так как он автоматически обрабатывает обновления интерфейса. Если же требуется просто прокручиваемое пространство для элементов, например, картинок или статического контента, то лучше подойдет ScrollView. ScrollView не предоставляет таких встроенных возможностей, как List, например, работу с удалением или сортировкой элементов. Для оптимизации работы с большими списками, можно использовать LazyVStack в сочетании с ScrollView, чтобы улучшить производительность.
Как List в SwiftUI управляет производительностью при большом количестве данных?
List в SwiftUI оптимизирован для работы с большими объемами данных, что позволяет эффективно управлять производительностью. Когда элементы списка не видны на экране, SwiftUI автоматически освобождает память, используя механизм ленивой загрузки. Это значит, что только те элементы, которые находятся в области видимости, загружаются и отображаются. Таким образом, при большом количестве строк приложение не будет загружать все данные сразу, что значительно снижает нагрузку на память и повышает производительность. Также можно использовать другие компоненты, такие как LazyVStack, для дополнительной оптимизации работы с большими списками. Важно помнить, что при использовании динамических данных в List важно правильно настроить идентификацию элементов через Identifiable или уникальные id, чтобы изменения в данных корректно отражались в интерфейсе.
