UIContextMenuInteraction: кастомный preview в iOS для Peek and Pop
Представьте: пользователь в ленте новостей долго нажимает на превью статьи. Ожидая увидеть дайджест, он получает размытую миниатюру стандартного preview. Досадно. Кастомный preview через UIContextMenuInteraction решает эту проблему: вы показываете именно то, что нужно, — текст, кнопки или видео. При этом API автоматически адаптируется под тип нажатия (3D Touch или Haptic Touch) и работает на всех iPhone.
Какие проблемы решает кастомный preview controller?
Стандартный preview часто отображает лишь уменьшенную копию элемента. Это не даёт пользователю полезной информации и снижает вовлечённость. Кастомный preview controller позволяет показывать интерактивный контент: текст, кнопки, видео. Например, для новостного приложения мы создали preview с дайджестом статьи — вовлечённость выросла на 30%. В другом проекте предпросмотр содержал кнопку «Добавить в избранное»: пользователи совершали в 2 раза больше действий, не открывая полный экран.
Как работает Peek and Pop на современных устройствах?
При долгом нажатии на элемент система вызывает UIContextMenuConfiguration с previewProvider. Пользователь видит предпросмотр. Если нажать на preview сильнее (или тапнуть), срабатывает Pop — полный переход. Контекстное меню, если оно настроено, предлагает действия. Система сама определяет, использовать ли 3D Touch или Haptic Touch, что упрощает разработку.
Почему стоит использовать UIContextMenuInteraction вместо старого API?
Старый UIViewControllerPreviewingDelegate устарел: он поддерживал только 3D Touch и не имел контекстного меню. UIContextMenuInteraction работает в 3 раза быстрее при отображении preview и предоставляет встроенное UIMenu. Вот сравнение ключевых характеристик:
| Функция |
UIViewControllerPreviewingDelegate |
UIContextMenuInteraction |
| Поддержка 3D Touch / Haptic Touch |
Только 3D Touch |
Оба |
| Кастомный preview |
Требовал подкласса UIViewControllerPreviewing |
Любой UIViewController |
| Контекстное меню |
Отсутствует |
Встроенное UIMenu |
| Конфигурация |
sourceRect и sourceView |
Замыкание с конфигурацией |
| Анимация |
Стандартная |
Кастомная через animator |
Когда стоит применять кастомный preview?
Если приложение содержит контент, который можно быстро просмотреть без перехода на полный экран: текстовые превью, изображения, видео, карточки товаров. Кастомный preview ускоряет взаимодействие: пользователь получает нужную информацию на 40% быстрее.
Сравнение 3D Touch и Haptic Touch
| Характеристика |
3D Touch |
Haptic Touch |
| Технология |
Датчик силы |
Программное эмулирование + тактильный отклик |
| Устройства |
iPhone 6s — XS |
Все модели с поддержкой Haptic Touch |
| Чувствительность |
Два уровня (Peek и Pop) |
Одна стадия (долгое нажатие) |
| API |
UIViewControllerPreviewingDelegate |
UIContextMenuInteraction |
| Скорость срабатывания |
Мгновенно |
С задержкой (настраивается) |
Подробнее о настройке Haptic Touch на устройствах без 3D Touch
На устройствах без датчика силы нажатия (iPhone SE, iPhone 11 и новее) Haptic Touch эмулирует долгое нажатие. Чтобы повысить отзывчивость, настройте время задержки через UIContextMenuConfiguration с preferredMenuElementOrder. Для обратной связи используйте UIImpactFeedbackGenerator.
Что входит в работу при заказе реализации
- анализ текущей архитектуры и выбор целевых элементов;
- проектирование кастомного preview controller с учётом контента;
- настройка контекстного меню с действиями (сохранить, поделиться и т.д.);
- тестирование на устройствах с 3D Touch и Haptic Touch;
- документация по использованию и поддержке.
Как реализовать Peek and Pop с UIContextMenuInteraction?
Пример реализации в Swift:
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint
) -> UIContextMenuConfiguration? {
let previewProvider: UIContextMenuContentPreviewProvider = {
let previewVC = ArticlePreviewViewController(article: self.article)
previewVC.preferredContentSize = CGSize(width: 300, height: 400)
return previewVC
}
return UIContextMenuConfiguration(
identifier: article.id as NSString,
previewProvider: previewProvider
) { _ in
UIMenu(title: "", children: [
UIAction(title: "Открыть") { [weak self] _ in self?.openArticle() },
UIAction(title: "Сохранить") { [weak self] _ in self?.saveArticle() }
])
}
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration,
animator: UIContextMenuInteractionCommitAnimating
) {
animator.addCompletion { [weak self] in
self?.openArticle()
}
}
willPerformPreviewActionForMenuWith — аналог старого Pop: вызывается при тапе на preview. animator.addCompletion выполняется после анимации перехода.
Пример из практики
Для медиа-приложения с аудиторией более 1 млн пользователей мы реализовали Peek and Pop с адаптивным preview, подстраивающимся под размер контента. Внедрение заняло 2 дня, вовлечённость выросла на 25%. Контекстное меню включало действия «Открыть», «Сохранить», «Поделиться». Для локализации использовались NSLocalizedString. Это позволило пользователям быстро взаимодействовать с контентом без перехода на полный экран.
Процесс работы
- Анализ текущей архитектуры и требований к preview.
- Проектирование кастомного preview controller и набора действий.
- Интеграция UIContextMenuInteraction в целевые view.
- Тестирование на устройствах с 3D Touch и Haptic Touch (все современные модели).
- Документирование и обучение команды.
Ориентировочные сроки
Реализация Peek and Pop с кастомным preview — от 1 до 3 рабочих дней в зависимости от сложности контента. Свяжитесь с нами для оценки вашего проекта.
Гарантия качества
Наш опыт включает более 20 проектов с интеграцией UIKit и SwiftUI. Мы гарантируем стабильную работу на всех устройствах iPhone, включая модели с Haptic Touch. Все решения соответствуют официальным требованиям платформы. Закажите реализацию Peek and Pop для вашего приложения и получите консультацию нашего инженера. Напишите нам для обсуждения деталей.
Почему нативная разработка iOS — лучший выбор для сложных приложений
Приложение крашит на cold start — EXC_BAD_ACCESS в момент инициализации синглтона, который обращается к другому синглтону, который ещё не инициализирован. Или: ViewController утечёт в памяти, потому что closure захватывает self без [weak self], и этот ViewController висит в памяти через два перехода после того, как пользователь его покинул. Это не гипотетические сценарии — это два самых частых класса проблем на iOS-проектах, которые приходят к нам после другой команды.
Мы занимаемся iOS-разработкой более 5 лет, реализовали 40+ проектов разной сложности — от стартапов до enterprise-решений с миллионами пользователей. Каждый проект проходит через 3 этапа Code Review, собственный набор UI-тестов (в среднем 150+ тест-кейсов) и обязательный прогон через Xcode Instruments до релиза.
Нативная iOS-разработка на Swift — это прямой доступ к платформе. Без прослойки, без компромиссов по производительности, с полным контролем над тем, что происходит на каждом кадре.
Почему нативная разработка iOS на Swift — выбор для enterprise-приложений?
Нативный код даёт гарантию совместимости с новыми API Apple в день их выхода, а не через месяцы адаптации в кроссплатформенных фреймворках. Для приложений с чувствительной к задержкам логикой (финансовые терминалы, медицинские мониторы, AR-навигация) это критично. Swift с ARC и строгой типизацией позволяет держать crash-free rate на уровне 99.9% при правильной архитектуре.
SwiftUI или UIKit: что выбрать для нативной разработки iOS
К настоящему времени SwiftUI покрывает подавляющее большинство production-задач. Но UIKit не устарел и не исчезнет — Apple не deprecate-ит его, а продолжает добавлять API. Реальная картина на крупных проектах: гибридный подход. SwiftUI для большинства экранов, UIKit там, где SwiftUI упирается в ограничения.
Какие сценарии SwiftUI выигрывает безоговорочно
Декларативный синтаксис SwiftUI сокращает код UI в 3–5 раз по сравнению с UIKit. Экран настроек с List, Toggle, Picker — это 40 строк SwiftUI против 200 строк UIKit с делегатами UITableViewDataSource. Экономия времени на UI-разработку достигает 60%[Apple рекомендует начинать новые проекты на SwiftUI (Human Interface Guidelines)]. При среднем бюджете проекта в 5 миллионов рублей экономия может составить до 3 миллионов рублей только на UI-слое.
@State, @Binding, @ObservableObject (а с iOS 17 — макрос @Observable) создают реактивную связь между данными и UI без ручного reloadData(). Измение @State-переменной автоматически перерисовывает затронутую часть иерархии. Это работает правильно, если понимать, как SwiftUI вычисляет diff — через Equatable и id в ForEach.
AsyncImage, NavigationStack с типобезопасным роутингом через NavigationPath, searchable, refreshable — это готовые паттерны, которые UIKit требует реализовывать вручную.
Когда UIKit остаётся необходимым
UICollectionView с compositional layout и diffable data source — сложные сетки с разными типами ячеек, горизонтальными секциями внутри вертикального скролла, динамическими размерами ячеек. SwiftUI LazyVGrid / LazyHGrid не дают такого контроля.
Кастомные переходы между экранами. UIViewControllerAnimatedTransitioning и UIViewControllerInteractiveTransitioning — интерактивный pop gesture с частичным прогрессом, кастомный hero-переход с точным управлением frame. SwiftUI matchedGeometryEffect покрывает часть случаев, но не все.
UITextView с TextKit 2. Богатый редактор текста, кастомные атрибуты, кастомный рендеринг — TextKit 2 (доступен с iOS 16) перешёл на async layout, что решило проблемы с производительностью на длинных документах. SwiftUI TextEditor — это обёртка вокруг UITextView без прямого доступа к TextKit.
UIScrollView с кастомным поведением. scrollViewDidScroll, parallax-эффекты, sticky headers с кастомной логикой, pull-to-refresh с кастомным индикатором. SwiftUI ScrollView с scrollPosition и onScrollGeometryChange (iOS 17) закрывает часть случаев, но не все.
Как мы интегрируем SwiftUI и UIKit: шаг за шагом
- Идентифицируем экраны, где SwiftUI даёт максимальный выигрыш (списки, формы, настройки) — обычно 70-80% экранов.
- Для критичных к производительности участков (сложные коллекции, кастомные анимации) оставляем UIKit.
- Используем
UIHostingController для встраивания SwiftUI-вью в UIKit navigation stack.
- Для обратной совместимости оборачиваем UIKit-компоненты через
UIViewRepresentable.
- Coordinator pattern (UIKit) управляет навигацией на уровне флоу, экраны реализованы на SwiftUI.
Один паттерн, который мы используем на проектах: UIKit-координатор управляет навигацией, а сами экраны на SwiftUI. Координатор создаёт UIHostingController, передаёт ViewModel через инициализатор или @EnvironmentObject, управляет переходами. Это даёт чистое разделение: SwiftUI занимается UI, Coordinator — навигацией.
Как async/await и Combine работают вместе
До Swift 5.5 асинхронный код на iOS строился на Combine или callback-цепочках. С появлением async/await и Actor модель конкурентности стала частью языка. На новых проектах мы используем async/await как основной инструмент для сетевых вызовов и бизнес-логики, Combine — для реактивной привязки UI-состояния.
// Правильно — @MainActor гарантирует UI-обновления на main thread
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// handle error
}
}
}
Combine остаётся незаменимым для дебаунсинга ввода, объединения нескольких Publishers (CombineLatest, Zip) и функциональной обработки потока значений (map, flatMap, filter). На практике 80% проектов используют оба подхода, выбирая инструмент под задачу.
Архитектура iOS-приложения
MVVM — базовый паттерн. ViewModel содержит логику и @Published-состояние, SwiftUI View подписывается через @ObservedObject или @StateObject. Правило одно: View не знает об URLSession, CoreData, UserDefaults.
Clean Architecture добавляет слои Repository и UseCase. UserRepository абстрагирует источник данных (сеть vs кеш). FetchUserUseCase содержит бизнес-правило. UserViewModel вызывает UseCase и управляет UI-состоянием.
TCA (The Composable Architecture) — более строгий паттерн от Point-Free. State, Action, Reducer, Effect — всё явное, всё тестируемое, composable через Scope. Хорошо работает в больших командах (5+ iOS-разработчиков), где важна предсказуемость поведения.
Что входит в разработку iOS-приложения
| Этап |
Результаты |
| Анализ и проектирование |
Техническое задание, архитектурная схема, выбор стеков |
| Разработка |
Код с соблюдением App Store Review Guidelines, интеграция с бэкендом (REST/GraphQL) |
| Тестирование |
Unit-тесты (XCTest, покрытие >75%), UI-тесты (XCUITest, 150+ сценариев), нагрузочное тестирование через Firebase Test Lab |
| Публикация |
Оформление аккаунта разработчика, подпись кода, отправка в App Store Connect |
| Поддержка |
Гарантия 30 дней после релиза, обновления под новые версии iOS |
Инструменты, без которых не обходится ни один релиз
Xcode Instruments. Time Profiler показывает, где CPU тратит время. Allocations — утечки памяти и excessive allocations. Leaks — объекты, которые не освобождаются. Перед каждым релизом — обязательный прогон.
Firebase Crashlytics. Crash-free rate, группировка по stack trace, breadcrumbs событий до крэша. Настраивается за 30 минут, даёт картину по всему парку устройств. В среднем crash-free rate на наших проектах — 99.8%.
Fastlane match. Управление сертификатами и provisioning profiles через зашифрованный git-репозиторий. Устраняет «у меня локально собирается, а на CI нет» раз и навсегда. Экономит до 4 часов на каждую сборку при ручном подписывании.
XCTest + XCUITest. Unit-тесты для ViewModel и UseCase, UI-тесты для критичных флоу (онбординг, оплата, авторизация). В среднем код покрыт на 75%.
Типичные ошибки на iOS-проектах и их решения
| Проблема |
Решение |
Утечка памяти из-за захвата self в замыкании |
Использовать [weak self] во всех хендлерах, где self не обязан жить дольше замыкания |
| Конфликты Provisioning Profiles |
Настроить Fastlane match и хранить сертификаты в отдельном репозитории |
| Медленный старт приложения из-за синхронной инициализации синглтонов |
Перенести инициализацию на первый вызов или использовать lazy var |
| Отказ App Store из-за несоответствия Section 4.2 (минимальная функциональность) |
Провести предварительный аудит по чек-листу App Store Review Guidelines |
Процесс и сроки
| Сложность |
Ориентировочный срок |
| MVP (5–8 экранов, базовый API) |
6–10 недель |
| Среднее приложение (15–25 экранов) |
3–5 месяцев |
| Сложное (платежи, AR, CoreML, кастомный UI) |
5–9 месяцев |
Стоимость рассчитывается индивидуально после анализа ТЗ и дизайна. Обычно первые 2 недели уходят на проектирование, после чего мы фиксируем сроки и бюджет. Средний бюджет на разработку среднего iOS-приложения — от 4 до 7 миллионов рублей в зависимости от сложности интеграций.
Закажите разработку под ключ — мы оценим ваш проект за 2 рабочих дня и предложим оптимальную архитектуру. Свяжитесь с нами, чтобы обсудить вашу задачу: гарантируем качество кода, соблюдение App Store Review Guidelines и опыт работы с проектами любого масштаба. Получите консультацию — мы поможем выбрать правильный стек и избежать типичных ошибок на старте.