Реалізація Swipe Actions для елементів списку в мобільній програме
Swipe-to-delete в iOS Mail появився в 2007 році та досі залишається одним із найкопійованіших паттернів. Звучить просто — поки не почнеш робити це правильно на обох платформах одночасно.
Нативна реалізація проти кастомної
На iOS UITableView має вбудований механізм через UISwipeActionsConfiguration. Метод tableView(_:trailingSwipeActionsConfigurationForRowAt:) повертає конфігурацію з масивом UIContextualAction. Кожна дія має стиль .normal або .destructive (деструктивна автоматично фарбується червоним та показує підтвердження). leadingSwipeActionsConfigurationForRowAt — для свайпу вправо.
Для UICollectionView swipe-дій з коробки немає — або використовуй UICollectionViewListConfiguration (доступна iOS 14+), або реалізуй через UILongPressGestureRecognizer + UIPanGestureRecognizer з кастомною логікою threshold та velocity. UICollectionViewListConfiguration — переважний шлях для нових програм.
У SwiftUI — модифікатор swipeActions(edge:allowsFullSwipe:content:) на елементі List. allowsFullSwipe: true дозволяє повний свайп для першої дії. Обмеження: недоступно до iOS 15, при цільовому нижчі потрібен fallback через UIViewRepresentable.
На Android нативного компонента для свайп-дій у RecyclerView немає. Стандартний підхід — ItemTouchHelper з реалізацією ItemTouchHelper.SimpleCallback. Переоприділи onChildDraw() для отрисовки фону та іконок при свайпе, onSwiped() для обробки події. Важливий нюанс: onChildDraw викликається на кожний кадр при русі — вся отрисовка повинна бути максимально дешевою, Canvas операції без allocations.
З Jetpack Compose — бібліотека compose-foundation містить SwipeToDismiss з material3. Для повноцінних дій ліворуч та праворуч — rememberSwipeToDismissBoxState() та SwipeToDismissBox. Анімація будується на Animatable + LaunchedEffect.
Де розробники втрачають час
Головна проблема — стан ячейки після свайпу при переиспользовании. У UITableView при dequeueReusableCell ячейка повертається в вихідне положення автоматично. Але якщо робиш кастомний свайп через gesture recognizer — transform та alpha мають скидатися в prepareForReuse(). Забув — з'являються ghost-артефакти в ячейках після скролу.
На Android з ItemTouchHelper аналогічна історія: після notifyItemRemoved() індекси зміщуються, і якщо не пересчітати позицію в коллбеку — видаляється не той елемент. Плюс анімація відскоку при скасуванні свайпу виглядає дерев'яно без кастомізації getAnimationDuration().
Ще один сценарій — конфлікт із горизонтальним scroll-контейнером всередину ячейки. UIScrollView та UITableView мають конкуруючі gesture recognizers. Розв'язується через gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) з логікою приоритету по velocity direction на старті жесту.
Що входить у роботу
Реалізуємо свайп-дії для UITableView / UICollectionView / SwiftUI List на iOS та RecyclerView / Compose на Android. Для Flutter — flutter_slidable з підтримкою ActionPane з обох боків. Для React Native — react-native-gesture-handler з компонентом Swipeable на основі Reanimated 2.
Охоплюємо edge cases: конфлікти жестів, стан при переиспользовании, haptic при destructive action, accessibility (VoiceOver/TalkBack мають озвучувати доступні дії через accessibilityCustomActions).
Термін: 1 день — стандартні свайп-дії на одній платформі. 2–3 дні — кросплатформенна реалізація з кастомними анімаціями та повним покриттям edge cases.







