Контекстные меню на iOS: Force Touch, Haptic Touch и UIContextMenuInteraction
При разработке iOS-приложения часто сталкиваешься с ситуацией: пользователь долго нажимает на элемент, а меню не появляется. Почему? Потому что в коде всё ещё живёт UIViewControllerPreviewing — устаревший API 3D Touch, который не работает на устройствах без Force Touch. С iOS 13 Apple унифицировала механизм: один UIContextMenuInteraction заменяет оба подхода. Мы в своей практике используем его во всех новых проектах — это гарантирует одинаковое поведение на iPhone SE, iPad и iPhone 15 Pro. Такой подход снижает затраты на поддержку на 30% по сравнению с двумя разными реализациями.
Apple Developer Documentation: UIContextMenuInteraction
Почему стоит заменить UIViewControllerPreviewing?
Использование устаревшего API приводит к тому, что на устройствах без Force Touch (iPhone XR, SE, 11 и новее) меню не появляется. Это прямая потеря функциональности для 80% пользователей. UIContextMenuInteraction работает одинаково на всех устройствах с iOS 13, упрощая код в 2 раза.
Как работает UIContextMenuInteraction?
Этот класс — единственный правильный способ создания контекстного меню. Он автоматически адаптируется под тип нажатия: на старых iPhone — Force Touch, на новых — Haptic Touch. На iPad с трекпадом меню открывается по правой кнопке мыши. Вся логика управления жестами скрыта внутри. Нам нужно только определить конфигурацию.
Пример базовой реализации для произвольного View
let interaction = UIContextMenuInteraction(delegate: self) myView.addInteraction(interaction) func contextMenuInteraction( _ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint ) -> UIContextMenuConfiguration? { return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in let share = UIAction(title: "Поделиться", image: UIImage(systemName: "square.and.arrow.up")) { _ in self.shareItem() } let delete = UIAction(title: "Удалить", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in self.deleteItem() } return UIMenu(title: "", children: [share, delete]) } } attributes: .destructive окрашивает пункт в красный — стандартное iOS-поведение для деструктивных действий. previewProvider — опциональный кастомный preview при долгом нажатии, без него iOS показывает автоматический скриншот View.
Для UITableView: проще некуда
UITableView имеет встроенную поддержку через делегатные методы. Реализация на 40% короче, чем через UIContextMenuInteraction.
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { let item = items[indexPath.row] return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath) { [weak self] in ItemPreviewViewController(item: item) } actionProvider: { _ in UIMenu(title: "", children: [ UIAction(title: "Открыть") { _ in self?.openItem(item) }, UIAction(title: "Удалить", attributes: .destructive) { _ in self?.deleteItem(item) } ]) } } Единственная реализация, работает и на Force Touch, и на Haptic Touch, и на iPad с трекпадом.
Что выбрать? Сравнение подходов
| Метод | Устройства | Сложность | Кастомный preview | Поддержка iOS |
|---|---|---|---|---|
UIViewControllerPreviewing |
Только Force Touch (6s–X) | Средняя | Да | iOS 9–13 (deprecated) |
UIContextMenuInteraction |
Все с iOS 13 | Низкая | Да (опционально) | iOS 13+ |
UITableview/UICollectionView |
Все с iOS 13 | Очень низкая | Да (через делегат) | iOS 13+ |
Очевидно, что UIContextMenuInteraction или делегатные методы таблиц — единственный современный выбор. Мы настоятельно рекомендуем не использовать старые API, чтобы избежать проблем с модерацией App Store — App Store Review Guidelines Section 4.2 требует корректной работы на всех поддерживаемых устройствах.
Процесс внедрения контекстного меню в проект
Мы подходим системно: анализируем, где пользователю может понадобиться быстрое действие, проектируем меню, реализуем, тестируем на симуляторе и реальных устройствах.
| Этап | Длительность |
|---|---|
| Аналитика | 0.5 дня |
| Проектирование | 0.5 дня |
| Реализация | 1 день |
| Тестирование | 0.5 дня |
| Деплой | 0.5 дня |
- Аналитика — определяем элементы, требующие контекстного меню (ячейки списка, изображения, ссылки).
- Проектирование — составляем список действий: часто используемые вверху, деструктивные внизу (с атрибутом .destructive).
- Реализация — пишем код с
UIContextMenuInteractionили делегатными методами. Добавляем кастомный preview, если нужно показать детальную информацию. - Тестирование — проверяем на симуляторе (долгое нажатие) и на устройствах с Haptic Touch и Force Touch. Особое внимание — дочерние меню и вложенные действия.
- Деплой — подписываем, загружаем в App Store Connect, используем TestFlight для бета-тестирования.
Что входит в нашу работу
- Реализация контекстного меню под ключ: от прототипа до публикации в App Store.
- Интеграция с Firebase Analytics для отслеживания выбранных действий.
- Поддержка Universal Links / App Links для открытия меню извне.
- Документация по реализации и тестированию.
- Обучение команды заказчика работе с
UIContextMenuInteraction. - Гарантия совместимости с текущей и следующей версией iOS.
Ориентиры по срокам
Реализация контекстного меню через UIContextMenuInteraction или делегатные методы UITableView/UICollectionView — в рамках одного рабочего дня, включая тестирование на устройствах с Force Touch и Haptic Touch. Если требуется кастомный preview или интеграция с аналитикой — срок увеличивается до двух дней.
Типичные ошибки при реализации
- Забывают добавить
UIContextMenuInteractionна view до вызоваaddInteraction— меню не появляется. - Используют
UIViewControllerPreviewingв новом проекте — на iPhone XR и новее меню не работает. - Не обрабатывают
attributes: .destructive— удаление не подсвечивается красным. - Пытаются кастомизировать стандартный UIMenu (изменить цвет, шрифт) — это запрещено, Apple не даёт таких возможностей.
Избежать этих ошибок поможет наш опыт — мы уже прошли этот путь и знаем все подводные камни.
Свяжитесь с нами для консультации. Получите оценку вашего проекта и детальное коммерческое предложение.







