UIPresentationController Tutorial: Getting Started

Learn how to build custom view controller transitions and presentations with this UIPresentationController tutorial. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating the UIPresentationController

Sit back and picture this for a moment: The presented controller will take up two-thirds of the screen and the remaining third will show as dimmed. To dismiss the controller, you just tap on the dimmed portion. Can you picture it?

Okay, clear your head and come back to the project. In this part, you’ll take care of three critical pieces: subclass, dimming view and customizing the transition.

Creating and Initializing a UIPresentationController Subclass

Go to File ▸ New ▸ File…, choose iOS ▸ Source ▸ Cocoa Touch Class and click Next. Set the name to SlideInPresentationController, make it a subclass of UIPresentationController and set the language to Swift.

Click Next and set the group to Presentation.

Click Create to make your new file and add the following inside the class definition in SlideInPresentationController.swift:

//1
// MARK: - Properties
private var direction: PresentationDirection

//2  
init(presentedViewController: UIViewController,
     presenting presentingViewController: UIViewController?,
     direction: PresentationDirection) {
  self.direction = direction

  //3
  super.init(presentedViewController: presentedViewController,
             presenting: presentingViewController)
}

Here’s what this does:

  1. Declares direction to represent the direction of the presentation.
  2. Declares an initializer that accepts the presented and presenting view controllers, as well as the presentation direction.
  3. Calls the designated initializer for UIPresentationController.

Setting Up the Dimming View

As mentioned before, the presentation controller will have a dimmed background. Add the following to SlideInPresentationController, just above direction:

private var dimmingView: UIView!

Next, add the following extension:

// MARK: - Private
private extension SlideInPresentationController {
  func setupDimmingView() {
    dimmingView = UIView()
    dimmingView.translatesAutoresizingMaskIntoConstraints = false
    dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
    dimmingView.alpha = 0.0
  }
}

Here you create the dimming view, prepare it for Auto Layout and set its background color. Notice you don’t add it to a superview yet. You’ll do that when the presentation transition starts, as you’ll see later in this section.

That presented controller needs to make itself scarce when you tap the dimmed view. Add the following beneath setupDimmingView() to make that happen:

@objc func handleTap(recognizer: UITapGestureRecognizer) {
  presentingViewController.dismiss(animated: true)
}

Here you create a UITapGestureRecognizer handler that dismisses the controller.

Of course, you’ll need to write that UITapGestureRecognizer, so add the following to the bottom of setupDimmingView():

let recognizer = UITapGestureRecognizer(
  target: self, 
  action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)

This adds a tap gesture to the dimming view and links it to the action method you just added.

Finally, add a call to setupDimmingView() at the end of init(presentedViewController:presenting:direction:):

setupDimmingView()

Override Presentation Controller Methods

Before you can start customizing the transition, you have to override four methods and a property from UIPresentationController. The default methods don’t do anything, so there’s no need to call super.

First, for a smooth transition, override presentationTransitionWillBegin() to make the dimming view fade in along with the presentation. Add the following code to the main class definition inside of SlideInPresentationController.swift:

override func presentationTransitionWillBegin() {
  guard let dimmingView = dimmingView else {
    return
  }
  // 1
  containerView?.insertSubview(dimmingView, at: 0)

  // 2
  NSLayoutConstraint.activate(
    NSLayoutConstraint.constraints(withVisualFormat: "V:|[dimmingView]|",
      options: [], metrics: nil, views: ["dimmingView": dimmingView]))
  NSLayoutConstraint.activate(
    NSLayoutConstraint.constraints(withVisualFormat: "H:|[dimmingView]|",
      options: [], metrics: nil, views: ["dimmingView": dimmingView]))

  //3
  guard let coordinator = presentedViewController.transitionCoordinator else {
    dimmingView.alpha = 1.0
    return
  }

  coordinator.animate(alongsideTransition: { _ in
    self.dimmingView.alpha = 1.0
  })
}

Here’s what this code does:

  1. UIPresentationController has a property named containerView. It holds the view hierarchy of the presentation and presented controllers. This section is where you insert the dimmingView into the back of the view hierarchy.
  2. Next, you constrain the dimming view to the edges of the container view so that it fills the entire screen.
  3. transitionCoordinator of UIPresentationController has a very cool method to animate things during the transition. In this section, you set the dimming view’s alpha to 1.0 along with the presentation transition.

Now, you’ll hide the dimming view when you dismiss the presented controller. Override of dismissalTransitionWillBegin() by adding this code after the previous overridden method:

override func dismissalTransitionWillBegin() {
  guard let coordinator = presentedViewController.transitionCoordinator else {
    dimmingView.alpha = 0.0
    return
  }

  coordinator.animate(alongsideTransition: { _ in
    self.dimmingView.alpha = 0.0
  })
}

Similar to presentationTransitionWillBegin(), you set the dimming view’s alpha to 0.0 alongside the dismissal transition. This gives the effect of fading the dimming view.

This next override will respond to layout changes in the presentation controller’s containerView. Add this code after the previous overridden method:

override func containerViewWillLayoutSubviews() {
  presentedView?.frame = frameOfPresentedViewInContainerView
}

Here you reset the presented view’s frame to fit any changes to the containerView frame.

Next, you’ll give the size of the presented view controller’s content to the presentation controller. Add this code after the previous overridden method:

override func size(forChildContentContainer container: UIContentContainer,
                   withParentContainerSize parentSize: CGSize) -> CGSize {
  switch direction {
  case .left, .right:
    return CGSize(width: parentSize.width*(2.0/3.0), height: parentSize.height)
  case .bottom, .top:
    return CGSize(width: parentSize.width, height: parentSize.height*(2.0/3.0))
  }
}

This method receives the content container and parent view’s size, and then it calculates the size for the presented content. In this code, you restrict the presented view to 2/3 of the screen by returning 2/3 the width for horizontal and 2/3 the height for vertical presentations.

In addition to calculating the size of the presented view, you need to return its full frame. To do this you’ll override frameOfPresentedViewInContainerView. Below the properties at the top of the class definition, add the following:

override var frameOfPresentedViewInContainerView: CGRect {
  //1
  var frame: CGRect = .zero
  frame.size = size(forChildContentContainer: presentedViewController, 
                    withParentContainerSize: containerView!.bounds.size)

  //2
  switch direction {
  case .right:
    frame.origin.x = containerView!.frame.width*(1.0/3.0)
  case .bottom:
    frame.origin.y = containerView!.frame.height*(1.0/3.0)
  default:
    frame.origin = .zero
  }
  return frame
}

Have a look at this code section-by-section:

  1. You declare a frame and give it the size calculated in size(forChildContentContainer:withParentContainerSize:).
  2. For .right and .bottom directions, you adjust the origin by moving the x origin (.right) and y origin (.bottom) 1/3 of the width or height.

With all the overrides done, you’re ready to put finishing touches on the transitions!

Implementing the Presentation Styles

Remember in the previous section how you created the transitioningDelegate? Well, it’s there but not doing much at the moment. It’s badly in need of some additional implementation.

Open SlideInPresentationManager.swift, locate the UIViewControllerTransitioningDelegate extension and add the following to it:

func presentationController(
  forPresented presented: UIViewController,
  presenting: UIViewController?,
  source: UIViewController
) -> UIPresentationController? {
  let presentationController = SlideInPresentationController(
    presentedViewController: presented,
    presenting: presenting,
    direction: direction
  )
  return presentationController
}

Here you instantiate a SlideInPresentationController with the direction from SlideInPresentationManager. You return it to use for the presentation.

Now you’re making things happen! Build and run the app. Tap Summer, Winter and Medal Count to see your fancy new presentation styles in action.

Medal count

Quite a difference, don’t you think? The new presentation styles look sharp, but all the presented views still slide in from the bottom.

medal_gif_02

The client wants the Summer and Winter menus to slide in from the side. You’ll need to flex your animation muscles to make this happen.