Chapters

Hide chapters

iOS Animations by Tutorials

Seventh Edition · iOS 15 · Swift 5.5 · Xcode 13

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

20. UINavigationController Custom Transition Animations
Written by Marin Todorov

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Using a navigation stack via a UINavigationController is a common way to let your app’s users navigate through your app’s UI. Pushing a new view controller onto the navigation stack or popping one off gives you a sleek animation with no work on your part. A new screen comes from the right and pushes away the old one with a slight lag:

The above screenshot shows how iOS pushes a new view controller onto the navigation stack in the Settings app: The new view slides in from the right to cover the old view and the new title fades in while the old title titles fades from view.

The navigation paradigm in iOS has become old hat to users, as the same animations have been used for many years. This frees you to embellish your navigation controller transitions without throwing the user off.

In much the same way you built custom presenting view controllers in the previous chapter, you can build custom transitions to push and pop new view controllers.

You’ll be working with the Logo Reveal project. In this chapter, you’ll add a custom transparent view that gives the user a glimpse of the content hidden behind:

If you worked through the previous chapter, you’ll find that custom navigation controller transitions feel quite similar to presenting view controllers.

Introducing Logo Reveal

Open the starter project for this chapter and select Main.storyboard. You’ll see the project features a navigation controller, main view controller, and a detail view controller.

It will look like this:

The navigation’s already been hooked up for you so you can focus on customizing your navigation controllers.

Build and run your project; tap anywhere on the default screen (MainViewController) to present the vacation packing list (DetailViewController):

Custom Navigation Transitions

UIKit lets you customize navigation transitions via the delegate pattern in almost the same way you do for presenting view controllers.

The Navigation Controller Delegate

Before you can implement the delegate methods, you’ll need to create the basic skeleton of the animator class.

class RevealAnimator: NSObject, UIViewControllerAnimatedTransitioning {

}
let animationDuration = 2.0
var operation: UINavigationController.Operation = .push
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  return animationDuration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

}
extension MainViewController: UINavigationControllerDelegate {

}
navigationController?.delegate = self
let transition = RevealAnimator()
func navigationController(_ 
  navigationController: UINavigationController,
  animationControllerFor 
  operation: UINavigationController.Operation,
  from fromVC: UIViewController, 
  to toVC: UIViewController) ->
  UIViewControllerAnimatedTransitioning? {
  transition.operation = operation
  return transition
}

Adding a Custom Reveal Animation

The plan for your custom transition animation is relatively simple. You’ll simply animate a mask on DetailViewController to make it look like the transparent part of the RW logo reveals the contents of the underlying view controller. You’ll have to juggle layers and some animation tasks, but it’s nothing you haven’t done so far in the book. Creating the transition animation will be an easy feat for an animation pro like you!

weak var storedContext: UIViewControllerContextTransitioning?
storedContext = transitionContext
let fromVC = transitionContext.viewController(forKey: 
  .from) as! MainViewController
let toVC = transitionContext.viewController(forKey: 
  .to) as! DetailViewController

transitionContext.containerView.addSubview(toVC.view)
toVC.view.frame = transitionContext.finalFrame(for: toVC)
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = 
  NSValue(caTransform3D: CATransform3DIdentity)
animation.toValue = 
  NSValue(caTransform3D:
  CATransform3DConcat(
    CATransform3DMakeTranslation(0.0, -10.0, 0.0),
    CATransform3DMakeScale(150.0, 150.0, 1.0)
  )
)

animation.duration = animationDuration
animation.delegate = self
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name:
  .easeIn)
class RevealAnimator: NSObject, UIViewControllerAnimatedTransitioning, CAAnimationDelegate {
...
}
let maskLayer: CAShapeLayer = RWLogoLayer.logoLayer()
maskLayer.position = fromVC.logo.position
toVC.view.layer.mask = maskLayer
maskLayer.add(animation, forKey: nil)

Taking Care of the Rough Edges

You likely noticed you can still see the original logo behind the zooming reveal logo. The easiest way to handle this is to run the reveal animation on the original logo as well. You already have the animation, so it’s no matter to reuse it. This will make the original logo grow with the mask, matching its shape exactly so it won’t be in the way.

fromVC.logo.add(animation, forKey: nil)

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
  if let context = storedContext {
    context.completeTransition(!context.transitionWasCancelled)
    // reset logo
  }
  storedContext = nil
}
let fromVC = context.viewController(forKey: .from) 
  as? MainViewController

fromVC?.logo.removeAllAnimations()
let toVC = context.viewController(forKey: .to) 
  as? DetailViewController
toVC?.view.layer.mask = nil

if
  operation == .push,
  let fromVC = transitionContext
    .viewController(forKey: .from) as? MainViewController,
  let toVC = transitionContext
    .viewController(forKey: .to) as? DetailViewController {

Key Points

  • To enable and customize custom navigation transitions, you adopt the UINavigationControllerDelegate protocol in one of your types and make it your navigation delegate.
  • To perform any custom navigation transition animations, you adopt the UIViewControllerAnimatedTransitioning protocol your transition’s animator.
  • As long as you wrap up your transition correctly and call into the neccessary UIKit methods at the end, you can successfully use layer animations for your custom transitions.
  • To preserve the context of the navigation transition and access it asynchronously throughout the transition’s duration, you can retain it in a UIViewControllerContextTransitioning property within your animator type.

Challenges

Challenge 1: Fade in the New View Controller

Right now the transition looks like a sharp cutout; the contents of the new view controller are visible instantly and make the whole animation look a bit clunky.

Challenge 2: Add Pop Transition

To create a pop transition, you’ll simply add a complementary else branch to the if statement inside animateTransition() of RevealAnimator. Inside the else branch you can add any animations you want, but don’t forget to call completeTransition() when you’re finished. Here’s how to create a simple shrink transition:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now