Реалізація Drag-and-Drop для мобільної програми
Перетаскування елементів виглядає як проста анімація, але за нею скривається складна координація: визначення початку жесту, створення візуальної копії перетаскуваного елемента, hit-testing зон скидання, оновлення моделі даних, анімація скасування. Кожен з цих кроків — потенційний баг.
iOS: UIKit drag-and-drop
З iOS 11 Apple надає вбудований Drag & Drop API через UIDragInteraction та UIDropInteraction. UIDragInteraction додається на source view, UIDropInteraction — на target view. Дані передаються через NSItemProvider — універсальний контейнер для будь-яких типів даних.
let dragInteraction = UIDragInteraction(delegate: self)
view.addInteraction(dragInteraction)
Ключовий метод делегата: dragInteraction(_:itemsForBeginning:) повинен повернути [UIDragItem] з NSItemProvider. Для внутрішнього drag & drop (в межах однієї програми) можна передавати будь-який об'єкт через властивість localObject UIDragItem — він не серіалізується та доступний миттєво.
Для UICollectionView та UITableView спеціалізовані UICollectionViewDragDelegate / UITableViewDragDelegate з методами collectionView(_:itemsForBeginningDragSession:at:). Вбудована підтримка reorder через collectionView(_:moveItemAt:to:) — не потрібно вручну управляти анімацією ячейок.
Проблеми з UICollectionView drag. При перетаскуванні колекція автоматично створює snapshot перетаскуваної ячейки. Якщо ячейка містить AVPlayerLayer або кастомні CALayer анімації — snapshot виглядає замороженим. Розв'язання: переопредилити dragPreviewParametersForItemAt: та повернути UIDragPreviewParameters з кастомним visiblePath.
У SwiftUI — модифікатори .draggable() та .dropDestination() з iOS 16. Дані мають відповідати протоколу Transferable. Кастомні типи — реалізуй Transferable через CodableRepresentation або DataRepresentation.
Android: drag & drop
На Android View.startDragAndDrop() (API 24+, раніше startDrag()). DragShadowBuilder створює візуальну тінь при перетаскуванні — переоприділи onDrawShadow() для кастомного виду. View.setOnDragListener() на target view обробляє події ACTION_DRAG_ENTERED, ACTION_DRAG_EXITED, ACTION_DROP, ACTION_DRAG_ENDED.
У Jetpack Compose — Modifier.dragAndDropSource {} та Modifier.dragAndDropTarget {} з'явилися в Compose 1.5. DragAndDropTransferData з ClipData як контейнер. Для drag всередину LazyColumn з reorder — бібліотека reorderable (Calvin Liang) або compose-reorderable.
Flutter: кастомна реалізація
Flutter не має вбудованого drag & drop на рівні нативних платформ. Draggable<T> та DragTarget<T> — основні віджети. Draggable створює childWhenDragging (placeholder на місці оригіналу) та feedback (віджет, що слідує за пальцем). DragTarget.onWillAcceptWithDetails() для перевірки, onAcceptWithDetails() для обробки.
Для reorder списків — ReorderableListView з Material бібліотеки. onReorder коллбек отримує oldIndex та newIndex. Важливий баг: коли newIndex > oldIndex роби newIndex -= 1 перед list.insert(). Документація це згадує, але розробники часто пропускають.
Поширені проблеми реалізації
Haptic при початку drag. На iOS UIFeedbackGenerator.impactOccurred() при longPressGestureRecognizer.state == .began. Користувач відчуває «захват» об'єкта. Без цього drag ощущається невагомим.
Scroll під час drag. Користувач тягає елемент до краю списку — список повинен прокручуватися. У UIKit — UIScrollView автоскролл при потраплянні в зону 50pt від краю. У Flutter — DragTarget із ScrollController.animateTo() в DragTarget.onWillAccept() коли курсор в межах 80px від краю.
Скасування drag. Якщо користувач кинув елемент не в допустиму зону — snap-back анімація на вихідну позицію. У UIKit це відбувається автоматично. У Flutter — управляємо через Draggable.onDraggableCanceled() з Velocity та координатами.
Стан моделі даних. При reorder — обновляй dataSource синхронно з анімацією, не після. Інакше при швидкому drag кількох елементів підряд індекси розʻїжджаються.
Термін: реалізація drag & drop для конкретного екрану (список з reorder або drag між двома зонами) — 2–3 дні. Складна система з кількома типами перетаскуваних об'єктів, cross-view drop та кастомними анімаціями — 5–7 днів.







