UIPresentationController Tutorial: Getting Started
Learn how to build custom view controller transitions and presentations with this UIPresentationController tutorial. By Ron Kliffer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIPresentationController Tutorial: Getting Started
30 mins
- Getting Started
- Core Concepts for iOS Transition
- Creating the Transitioning Delegate
- Setting Up the Framework
- Creating the UIPresentationController
- Creating and Initializing a UIPresentationController Subclass
- Setting Up the Dimming View
- Override Presentation Controller Methods
- Implementing the Presentation Styles
- Creating the Animation Controller
- Wiring Up the Animation Controller
- Adaptivity
- Overriding the presented controller
- Where To Go From Here?
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:
-
Declares
direction
to represent the direction of the presentation. - Declares an initializer that accepts the presented and presenting view controllers, as well as the presentation direction.
-
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:
-
UIPresentationController
has a property namedcontainerView
. It holds the view hierarchy of the presentation and presented controllers. This section is where you insert thedimmingView
into the back of the view hierarchy. - Next, you constrain the dimming view to the edges of the container view so that it fills the entire screen.
-
transitionCoordinator
ofUIPresentationController
has a very cool method to animate things during the transition. In this section, you set the dimming view’salpha
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:
-
You declare a frame and give it the size calculated in
size(forChildContentContainer:withParentContainerSize:)
. - 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.
Quite a difference, don’t you think? The new presentation styles look sharp, but all the presented views still slide in from the bottom.
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.