Ответ
Drag and Drop API для UICollectionView и UITableView — мощная системная фича, позволяющая пользователям интуитивно переупорядочивать контент внутри приложения и между приложениями.
Что позволяет делать:
- Перетаскивание ячеек внутри одной коллекции/таблицы.
- Перенос элементов между разными коллекциями/таблицами.
- Межпрограммный перенос данных между приложениями (например, изображение из Фото в ваше приложение).
- Поддержка мультитач-жестов для выбора и переноса нескольких элементов сразу.
Базовая реализация для UICollectionView:
-
Активация:
collectionView.dragInteractionEnabled = true collectionView.dragDelegate = self collectionView.dropDelegate = self -
Начало перетаскивания (Drag Delegate):
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { let item = dataModel.item(at: indexPath) let itemProvider = NSItemProvider(object: item.title as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) dragItem.localObject = item // Контекст для внутреннего переноса return [dragItem] } -
Обработка "дропа" (Drop Delegate):
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { let destinationIndexPath = coordinator.destinationIndexPath ?? ... for item in coordinator.items { if let sourceIndexPath = item.sourceIndexPath { // ВНУТРЕННИЙ ПЕРЕНОС: перемещаем объект в модели данных collectionView.performBatchUpdates { let movedItem = dataModel.remove(at: sourceIndexPath) dataModel.insert(movedItem, at: destinationIndexPath) collectionView.moveItem(at: sourceIndexPath, to: destinationIndexPath) } } else { // МЕЖПРОГРАММНЫЙ ПЕРЕНОС: создаем новый объект из данных item.dragItem.itemProvider.loadObject(ofClass: NSString.self) { (data, error) in if let text = data as? String { DispatchQueue.main.async { self.dataModel.insert(Item(title: text), at: destinationIndexPath) collectionView.insertItems(at: [destinationIndexPath]) } } } } } }
Ключевые преимущества:
- Нативные жесты и анимации (поднятие ячейки, кастомный превью).
- Высокая производительность, интегрированная с системой.
- Универсальность: работает с любыми типами данных через
NSItemProvider.
Ответ 18+ 🔞
Ну что за тема, блядь! Сидишь такой, пишешь приложение, а тут бац — пользователь хочет пальцем по экрану поводить и всё переставить, как ему вздумается. И ведь система уже всё придумала, эти ваши Drag and Drop API, но пока в них не влезешь, нихуя не понятно, как эта магия работает.
Смотри, вот тебе расклад по полочкам, без этой охуевшей документации от Apple, которая читается как инструкция к шаманскому бубну.
Про что речь вообще? Это ж целый цирк, ёпта! Пользователь может:
- Таскать ячейки туда-сюда внутри одной таблицы или коллекции — обычное дело.
- Перекидывать их между разными таблицами или коллекциями — уже интереснее.
- Вообще, блядь, тащить хуй знает что из другого приложения прямо к тебе! Картинку из «Фоток», текст из «Заметок» — вот это уже мощь.
- И да, можно сразу несколько штук пальцами обцепить и потащить — выглядит, как будто ты директор склада на сенсорном экране.
Как это впихнуть в UICollectionView? Всё проще, чем кажется, хоть и звучит страшно.
-
Включи мозг и свитч. Первым делом говоришь коллекции: «Проснись, сука, мы будем таскать!»
collectionView.dragInteractionEnabled = true // Главный тумблер! collectionView.dragDelegate = self // Тот, кто начнёт таскание collectionView.dropDelegate = self // Тот, кто примет упавшее -
Ловим момент, когда пользователь зацепил ячейку (Drag Delegate). Тут надо систему накормить данными, которые потащим. Главное — не обосраться с
NSItemProvider.func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { // 1. Достаём из своей модели данных то, что тащим let item = dataModel.item(at: indexPath) // 2. Заворачиваем это в "посылку", которую система поймёт let itemProvider = NSItemProvider(object: item.title as NSString) // 3. Создаём сам "перетаскиваемый предмет" let dragItem = UIDragItem(itemProvider: itemProvider) dragItem.localObject = item // Вот это, бля, важно! Пометка для ВНУТРЕННЕГО переноса. return [dragItem] // Отдаём системе один предмет (можно и несколько) } -
Принимаем "посылку", когда её бросили (Drop Delegate). А вот тут уже настоящая логика, где нужно решить: это перестановка внутри или нам подкинули что-то новое с улицы?
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { // Куда бросили? Если не попали в ячейку, придумываем куда (например, в конец). let destinationIndexPath = coordinator.destinationIndexPath ?? ... // Разбираем всё, что нам набросали for item in coordinator.items { // Если есть sourceIndexPath — значит, тащили ИЗ ЭТОЙ ЖЕ коллекции! if let sourceIndexPath = item.sourceIndexPath { // ВНУТРЕННИЙ ПЕРЕНОС: collectionView.performBatchUpdates { // 1. Выдёргиваем объект из модели let movedItem = dataModel.remove(at: sourceIndexPath) // 2. Втыкаем его на новое место dataModel.insert(movedItem, at: destinationIndexPath) // 3. Говорим коллекции анимировать это движение collectionView.moveItem(at: sourceIndexPath, to: destinationIndexPath) } } else { // А вот это уже МЕЖПРОГРАММНЫЙ ПЕРЕНОС! Нам подсунули что-то извне. // Качаем данные из этой "посылки" (itemProvider) item.dragItem.itemProvider.loadObject(ofClass: NSString.self) { (data, error) in if let text = data as? String { DispatchQueue.main.async { // Создаём новый объект в нашей модели self.dataModel.insert(Item(title: text), at: destinationIndexPath) // И просим коллекцию красиво его показать collectionView.insertItems(at: [destinationIndexPath]) } } } } } }
И в чём, блядь, соль-то?
- Всё уже готово. Жесты, анимация поднятия ячейки, превью — не надо это самому городить, ёпта.
- Летает быстро. Системная интеграция, не самопал на костылях.
- Универсально как хуй с винтом. Через
NSItemProviderможно передать почти что угодно — строку, картинку, файл, даже свой кастомный тип, если его правильно описать.
Вот и вся магия. Сначала кажется, что нихуя не понятно, но как разберёшься — оказывается, логично и даже удобно. Главное — не забыть про localObject для внутренних перемещений, а то будешь потом ебаться, почему данные слетают.