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

19. Presentation Controller & Orientation 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

Presenting view controllers is the common way to “move” the user focus modally from one contained piece of UI to another. When the user has finished making use of the presented UI they will choose to save their work, cancel the pending changes, or otherwise actively dismiss the UI. Whether you’re presenting the camera view controller, the address book, or one of your own custom-designed modal screens, you call the same UIKit method every time: present(_:animated:completion:). This method “gives up” the current screen to another view controller. The default presentation animation simply slides the new view up to cover the current one.

The illustration below shows a “New Contact” view controller sliding up over the list of contacts:

In this chapter, you’ll create your own custom presentation controller animations to replace the default one and liven up this chapter’s project.

Looking Through the Starter Project

Open the starter project for this chapter, an app called Beginner Cook. Select Main.storyboard to begin the tour:

The first view controller (ViewController) contains the app’s title and main description as well as a scroll view at the bottom, which shows the list of available herbs.

The main view controller presents HerbDetailsViewController whenever the user taps one of the images in the list; this view controller sports a background, a title, a description and some buttons to credit the image owner.

There’s already enough code in ViewController.swift and HerbDetailsViewController.swift to support the basic application.

Build and run the project to see how the app looks and feels:

Tap on one of the herb images, and the details screen comes up via the standard vertical cover transition. That might be OK for your garden-variety app, but your herbs deserve better!

Your job is to add some custom presentation controller animations to your app to make it blossom! You’ll replace the current stock animation with one that expands the tapped herb image to a full-screen view like so:

Roll up your sleeves, put your developer apron on and get ready for the inner workings of custom presentation controllers!

Behind the Scenes of Custom Transitions

UIKit lets you customize your view controller’s presentation via the delegate pattern; you simply make your main view controller (or another class you create specifically for that purpose) adopt UIViewControllerTransitioningDelegate.

Implementing Transition Delegates

Since the delegate’s task is to manage the animator object that performs the actual animations, you’ll first have to create a stub for the animator class before you can write the delegate code.

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  return 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

}
extension ViewController: UIViewControllerTransitioningDelegate {

}
// ...
herbDetails.transitioningDelegate = self // <-- Add this line
present(herbDetails, animated: true, completion: nil)
let transition = PopAnimator()
func animationController(
  forPresented presented: UIViewController, 
  presenting: UIViewController, 
  source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
  guard let selectedImage = selectedImage else {
   return nil
  }
  return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  return nil
}

Creating your Transition Animator

Open PopAnimator.swift; this is where you’ll add the code to transition between the two view controllers.

let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
return duration

Setting your Transition’s Context

It’s time to add some magic to animateTransition. This method has one parameter of type UIViewControllerContextTransitioning, which gives you access to the parameters and view controllers of the transition. Before you start working on the code itself, it’s important to understand what the animation context actually is.

Adding a Fade Transition

You’ll start with a simple fade transition to get a feel for custom transitions. Add the following code to animateTransition():

let containerView = transitionContext.containerView

let toView = transitionContext.view(forKey: .to)!
containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: duration, 
  animations: {
    toView.alpha = 1.0
  }, 
  completion: { _ in
    transitionContext.completeTransition(true)
  }
)

Adding a Pop Transition

You’re going to structure the code for the new transition slightly differently, so replace all the code in animateTransition() with the following:

let containerView = transitionContext.containerView
let herbView = presenting ?
  transitionContext.view(forKey: .to) : 
  transitionContext.view(forKey: .from)
guard let herbView = herbView else {
  transitionContext.completeTransition(false)
  return
}
let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame

let xScaleFactor = presenting ?
  initialFrame.width / finalFrame.width :
  finalFrame.width / initialFrame.width

let yScaleFactor = presenting ?
  initialFrame.height / finalFrame.height :
  finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(
  scaleX: xScaleFactor, 
  y: yScaleFactor)

if presenting {
  herbView.transform = scaleTransform
  herbView.center = CGPoint(
    x: initialFrame.midX,
    y: initialFrame.midY)
  herbView.clipsToBounds = true
}
if let toView = transitionContext.view(forKey: .to) {
  containerView.addSubview(toView)
}
containerView.bringSubviewToFront(herbView)

UIView.animate(
  withDuration: duration, 
  delay:0.0, 
  usingSpringWithDamping: 0.4, 
  initialSpringVelocity: 0.0,
  animations: {
    herbView.transform = self.presenting ?
      CGAffineTransform.identity : scaleTransform
    herbView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
  }, 
  completion: { _ in
    transitionContext.completeTransition(true)
  })

transition.originFrame = selectedImage.convert(selectedImage.bounds, to: nil)

transition.presenting = true
selectedImage!.isHidden = true

Adding a Dismiss Transition

All that’s left to do is dismiss the details controller. You’ve actually done most of the work in the animator already — the transition animation code does the logic juggling to set the proper initial and final frames, so you’re most of the way to playing the animation both forwards and backwards. Sweet!

transition.presenting = false
return transition

var dismissCompletion: (() -> Void)?
if !self.presenting {
  self.dismissCompletion?()
}
transition.dismissCompletion = {
  self.selectedImage?.isHidden = false
}

Device Orientation Transition

Note: This section of the chapter is optional; if you’re not interested in learning how to handle changes in device orientation in your view controllers, skip ahead directly to the challenges.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  super.viewWillTransition(to: size, with: coordinator)

}
coordinator.animate(
  alongsideTransition: { context in
    self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
  }, 
  completion: nil
)
self.positionListItems()

Key Points

  • You enable and configure a custom presentation transition by implementing the UIViewControllerTransitioningDelegate protocol in one of your types and making it the presentation delegate.
  • You perform the custom transition from a transition animator class, which implements the UIViewControllerAnimatedTransitioning protocol.
  • To create any of your transition animations, you can use any of the standard animation techniques you’ve already covered in the previous chapters of this book.

Challenges

There are two tiny imperfections in your presentation animation: you can see the detail view text up to the very last moment when it just disappears; in addition, the initial herb images have rounded corners, which makes the animation look jumpy at the end.

Challenge 1: Smooth the Transition Animation

Your first task is to fade the contents of the herb details view in or out as appropriate while transitioning. This corrects that awkward moment when the text of the detail view just disappears as it’s dismissed.

Challenge 2: Animate the Corner Radius

Finally you’ll animate the corner radius of the details view so that it matches the rounded corners of the herb images in the main view controller.

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