Создание анимаций переходов между экранами мобільного додатка
Перехід між екранами—момент, коли користувач або розуміє, куди він рухається, або губиться. Поганий перехід не просто невродний: він створює когнітивне навантаження. Стандартний pushViewController на iOS або startActivity на Android працюють, але не створюють ощущення зв'язності інтерфейсу.
Платформенні механізми навігаційних переходів
UIKit та кастомні transitions на iOS
UIKit надає два рівні кастомізації. Перший—UINavigationControllerDelegate з методом navigationController(_:animationControllerFor:from:to:). Повертаєш свій об'єкт, реалізуючий UIViewControllerAnimatedTransitioning, й отримуєш повний контроль над анімацією.
class SlideUpTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.38
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toVC = ctx.viewController(forKey: .to),
let fromVC = ctx.viewController(forKey: .from) else { return }
let container = ctx.containerView
let finalFrame = ctx.finalFrame(for: toVC)
toVC.view.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height)
container.addSubview(toVC.view)
UIView.animate(
withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.88,
initialSpringVelocity: 0.3,
options: [.curveEaseOut]
) {
toVC.view.frame = finalFrame
fromVC.view.alpha = 0.85
fromVC.view.transform = CGAffineTransform(scaleX: 0.96, y: 0.96)
} completion: { _ in
fromVC.view.transform = .identity
fromVC.view.alpha = 1
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
Spring damping 0.88 з velocity 0.3—приблизно те, що Apple використовує в рідних переходах. Менше—буде болтатися, більше—втратиться ефект пружності. Головна помилка: забути completeTransition(false) при відмові жестом назад. Без цього контролер зависає в промежуточному стані.
Другий рівень—UIViewControllerInteractiveTransitioning для жестової навігації. Зв'язуєш UIPercentDrivenInteractiveTransition з UIPanGestureRecognizer, оновлюєш update(_:) при кожній зміні позиції пальця.
SwiftUI: matchedGeometryEffect і NavigationTransition
SwiftUI дає matchedGeometryEffect(id:in:)—декларативний Shared Element Transition. Достатньо позначити одинаковим id вью на обох екранах в одному Namespace:
@Namespace var heroNamespace
// Список
Image(product.imageName)
.matchedGeometryEffect(id: product.id, in: heroNamespace)
// Детальний екран
Image(product.imageName)
.matchedGeometryEffect(id: product.id, in: heroNamespace)
iOS 18 додав NavigationTransition протокол та кілька готових ефектів через .navigationTransition(.zoom(...)). Це нативний zoom-transition як у Photos.app.
Jetpack Compose: AnimatedContent та SharedTransitionLayout
На Android з Compose переходи будуються через AnimatedContent всередині NavHost:
NavHost(
navController = navController,
startDestination = "list",
enterTransition = {
slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(300)
)
}
) { ... }
SharedTransitionLayout + sharedElement() модифікатор—аналог matchedGeometryEffect для Compose, з'явився в Compose 1.7. До цього Shared Element у Compose була болем: бібліотека Accompanist Transitions працювала, але з артефактами при швидких переходах.
Типові помилки
Freeze на першому кадрі. Бує коли destination view контроллер ще не завершив layout у момент початку анімації. Фікс: викликати toVC.view.layoutIfNeeded() до старту анімації.
Jumpy status bar при переході між екранами з різним preferredStatusBarStyle. UIKit перераховує стиль з затримкою. Рішення: установити modalPresentationCapturesStatusBarAppearance = true на presenting контролері.
Чорний прямокутник під прозорим NavigationBar при custom transition—відбувається, коли containerView.backgroundColor не установлено явно. Контейнер наслідує .systemBackground, але при анімації opacity можуть бути артефакти.
Платформенні гайдлайни: що не можна порушувати
Apple Human Interface Guidelines вимагають, щоб переходи були не довше 400ms для навігаційних дій. Android Material Design 3—300ms для container transforms. Перевищення сприймається як «повільний телефон», навіть якщо додаток технічно швидкий.
Modal presentations на iOS (.sheet) системно анімуються знизу вгору. Переопределяти це напрямок без веської причини—погана практика: користувач привик до цього паттерна.
Процес роботи
Розпочинаємо з аудиту поточних переходів та складання карти екранів з вказанням типу переходу для кожної пари. Далі—прототипування в Xcode/Android Studio з підбором параметрів spring-анімацій. Реалізація кастомних UIViewControllerAnimatedTransitioning / NavigationTransition / Compose transitions. Паралельно—інтерактивні переходи на жестах де це доцільно. Фінальне тестування на реальних пристроях (включаючи iPhone SE 2nd gen з меншим CPU) через Xcode Core Animation Instrument.
Ориєнтири по терміни
Базовий набір переходів для 3–5 типів екранів—2–3 робочих дні. Кастомний Hero-transition з інтерактивністю на жесті—від 3 до 5 днів.







