Mobile App Screen Transition Animations
Screen transition is a moment when user either understands where they are moving or gets lost. Bad transition is not just ugly: it creates cognitive load. Standard pushViewController on iOS or startActivity on Android work, but do not create sense of interface continuity.
Platform Navigation Transition Mechanisms
UIKit and Custom Transitions on iOS
UIKit provides two customization levels. First—UINavigationControllerDelegate with navigationController(_:animationControllerFor:from:to:) method. Return your object implementing UIViewControllerAnimatedTransitioning, get full control over animation.
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 with velocity 0.3—roughly what Apple uses in native transitions. Less—will oscillate, more—lose springiness effect. Main mistake: forget completeTransition(false) on gesture cancellation. Without it, controller hangs in intermediate state.
Second level—UIViewControllerInteractiveTransitioning for gesture navigation. Bind UIPercentDrivenInteractiveTransition with UIPanGestureRecognizer, update update(_:) on each finger position change.
SwiftUI: matchedGeometryEffect and NavigationTransition
SwiftUI provides matchedGeometryEffect(id:in:)—declarative Shared Element Transition. Just mark same id on views on both screens in one Namespace:
@Namespace var heroNamespace
// List
Image(product.imageName)
.matchedGeometryEffect(id: product.id, in: heroNamespace)
// Detail screen
Image(product.imageName)
.matchedGeometryEffect(id: product.id, in: heroNamespace)
iOS 18 added NavigationTransition protocol and several ready effects via .navigationTransition(.zoom(...)). This is native zoom-transition like in Photos.app.
Jetpack Compose: AnimatedContent and SharedTransitionLayout
On Android with Compose, transitions build via AnimatedContent inside 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() modifier—analog of matchedGeometryEffect for Compose, appeared in Compose 1.7. Before that, Shared Element in Compose was painful: Accompanist Transitions library worked, but with artifacts on fast transitions.
Typical Pitfalls
Freeze on first frame. Happens when destination view controller has not finished layout at animation start. Fix: call toVC.view.layoutIfNeeded() before animation start.
Jumpy status bar when transitioning between screens with different preferredStatusBarStyle. UIKit recalculates style with delay. Solution: set modalPresentationCapturesStatusBarAppearance = true on presenting controller.
Black rectangle under transparent NavigationBar on custom transition—happens when containerView.backgroundColor not set explicitly. Container inherits .systemBackground, but animation opacity may cause artifacts.
Platform Guidelines: What You Cannot Violate
Apple Human Interface Guidelines require transitions no longer than 400ms for navigation actions. Android Material Design 3—300ms for container transforms. Exceeding is perceived as "slow phone," even if technically fast.
Modal presentations on iOS (.sheet) are system animated bottom-up. Overriding this direction without reason—bad practice: user is used to this pattern.
Process
Start with audit of current transitions and map screens with transition type for each pair. Then—prototyping in Xcode/Android Studio with spring animation parameter tuning. Implementation of custom UIViewControllerAnimatedTransitioning / NavigationTransition / Compose transitions. In parallel—interactive transitions on gestures where appropriate. Final testing on real devices (including iPhone SE 2nd gen with less CPU) via Xcode Core Animation Instrument.
Timeline Guidelines
Basic set of transitions for 3–5 screen types—2–3 business days. Custom Hero-transition with gesture interactivity—3 to 5 days.







