Purpose
Implement a custom navigation animation that uses a "present-like" animation effect during push and pop operations across multiple view controllers.
- A pushes B (normal animation)
- B pushes C (custom present-like animation)
- C pops back to B (custom dismiss-like animation)
- Multiple view controllers may push to C, all using the same custom animations
Steps
Step 1: Create Custom Animation Controllers
- PresentAnimationController: Handles the animation effect when pushing to ViewControllerC.
- DismissAnimationController: Handles the animation effect when popping back from ViewControllerC.
// PresentAnimationController: Custom "present-like" animation
class PresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5 // Duration of the animation
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
return
}
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)
toVC.view.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) // Start position off-screen
containerView.addSubview(toVC.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toVC.view.frame = finalFrame // Animate to final position
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
// DismissAnimationController: Custom "dismiss-like" animation
class DismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5 // Duration of the animation
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
return
}
let containerView = transitionContext.containerView
let initialFrame = transitionContext.initialFrame(for: fromVC)
toVC.view.frame = initialFrame
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromVC.view.frame = initialFrame.offsetBy(dx: 0, dy: initialFrame.height) // Move off-screen
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
Step 2: Create a Common UINavigationControllerDelegate
Class
This delegate class handles all push and pop actions with custom animations.
class CustomNavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push, toVC is ViewControllerC {
return PresentAnimationController() // Use present-like animation for push to C
} else if operation == .pop, fromVC is ViewControllerC {
return DismissAnimationController() // Use dismiss-like animation for pop from C
}
return nil
}
}
Step 3: Set the UINavigationController
to Use This Delegate Class
In AppDelegate
or the main view controller, set the delegate
of UINavigationController
to the common delegate class.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let customNavDelegate = CustomNavigationControllerDelegate() // Create delegate instance
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController(rootViewController: ViewControllerA())
navigationController.delegate = customNavDelegate // Set the delegate
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
}
Step 4: Push to C from Any View Controller
Ensure that any view controllers pushing to ViewControllerC do so without needing additional configuration, as the common delegate handles all animation logic.
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "ViewController A"
let pushButton = UIButton(type: .system)
pushButton.setTitle("Push to B", for: .normal)
pushButton.addTarget(self, action: #selector(pushToB), for: .touchUpInside)
pushButton.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
view.addSubview(pushButton)
}
@objc func pushToB() {
let viewControllerB = ViewControllerB()
navigationController?.pushViewController(viewControllerB, animated: true)
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
title = "ViewController B"
let pushButton = UIButton(type: .system)
pushButton.setTitle("Push to C with Present Animation", for: .normal)
pushButton.addTarget(self, action: #selector(pushToC), for: .touchUpInside)
pushButton.frame = CGRect(x: 50, y: 200, width: 300, height: 50)
view.addSubview(pushButton)
}
@objc func pushToC() {
let viewControllerC = ViewControllerC()
navigationController?.pushViewController(viewControllerC, animated: true)
}
}
class ViewControllerC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .cyan
title = "ViewController C"
let backButton = UIButton(type: .system)
backButton.setTitle("Back to B", for: .normal)
backButton.addTarget(self, action: #selector(popToB), for: .touchUpInside)
backButton.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
view.addSubview(backButton)
}
@objc func popToB() {
navigationController?.popViewController(animated: true)
}
}
Result
- All pushes to ViewControllerC use the custom "present-like" animation.
- All pops from ViewControllerC use the custom "dismiss-like" animation.
- Simplifies management of navigation animations across multiple view controllers with a single, reusable delegate class.
Top comments (0)