Раньше в SwiftUI была четкая граница. Если вы хотели реализовать перетаскивание с помощью стандартных средств ваш выбор был List с его onMove. Для LazyVStack, HStack, Grid или кастомных макетов приходилось писать решение вручную. Не было никакого готового функционала для этого.
В iOS 27 это изменилось. SwiftUI получил новый API: reorderable() и reorderContainer. Теперь любой контейнер может поддерживать перетаскивание и перестановку элементов.
Как это работает:
Новый API состоит из двух частей. Первая - reorderable(). Она ставится на динамический контент, который может участвовать в перетаскивании. Чаще всего это ForEach.
ForEach(items) { item in
ItemRow(item: item)
}
.reorderable()
Вторая - reorderContainer(for:). Она определяет родительский контейнер, внутри которого разрешена перестановка.
.reorderContainer(for: Item.self) { difference in
items.apply(difference: difference)
}
Этот подход хорошо ложится в логику SwiftUI. Поведение элемента живет с динамическим контентом. Границы взаимодействия - с контейнером. Разделение четкое и предсказуемое.
Простой пример:
Допустим, у вас есть плейлист в виде кастомного вертикального списка. Раньше пришлось бы реализовывать перетаскивание вручную. Теперь это всего лишь несколько строк кода:
struct PlaylistView: View {
@State private var items: [PlaylistItem] = [...]
var body: some View {
ScrollView {
LazyVStack(alignment: .leading, spacing: 8 ) {
ForEach(items) { item in
Text(item.title)
.padding()
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.reorderable()
}
.padding()
.reorderContainer(for: PlaylistItem.self) { difference in
items.apply(difference: difference)
}
}
}
}
reorderable() ставится на ForEach, reorderContainer - на LazyVStack. SwiftUI берет на себя всю логику жестов, а вы только обновляете данные в замыкании.
Модель данных остается за вами:
API не сохраняет новый порядок автоматически. Он дает ReorderDifference описание того, что и куда переместили. А вы уже сами решаете, как применить это к модели.
.reorderContainer(for: PlaylistItem.self) { difference in
items.apply(difference: difference)
}
Можно применить apply(difference:) к массиву. А можно обработать вручную, если логика сложнее: проверка прав на перемещение, обновление sortIndex в базе данных, синхронизация с сервером. API не навязывает конкретный способ.
Управление доступностью перетаскивания:
У reorderContainer есть параметр isEnabled. Это удобный способ включать и выключать перестановку без условной логики на уровне жестов.
.reorderContainer(
for: PlaylistItem.self,
isEnabled: isEditing
) { difference in
items.apply(difference: difference)
}
Как только isEditing становится true, reorder активируется. Контейнер по-прежнему управляет областью взаимодействия, а состояние решает, активно ли оно.
🔗 Читать подробнее
💡 Вывод:
SwiftUI в iOS 27 наконец-то избавился от ограничения, которое долго раздражало разработчиков. Теперь перетаскивание работает в любом контейнере, а не только в List. Можно использовать reorderable() и reorderContainer с LazyVStack, HStack, Grid и кастомными макетами. API оставляет контроль над моделью данных за разработчиком, что важно для сложных приложений. И поддерживает перемещение между разными коллекциями.
Для пользователей это означает привычное поведение перетаскивания в любых интерфейсах. Для разработчиков - меньше ручного кода и более предсказуемый API.
Подписаться на канал:
➡️ Telegram | Max
Закрытый канал:
🚀 Мобильный трудоголик PRO