iOS Custom UIPresentationController

The purpose of this story is to demonstrate the creation of a custom presentation controller.

In my example, there will be a button on first view controller and when user taps on it, the detail view controller will be opened in a custom presentation controller.

Here’s the first view controller:

import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func buttonTapped(_ sender: UIButton) {
let vc = DetailViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
self.present(vc, animated: true, completion: nil)
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return CustomPresentationController(presentedViewController: presented, presenting: presenting)
}
}

We need to create our custom presentation controller. Custom presentation controller has a dimming view which covers all the screen. The height of the detail view controller is 2/3 of the presentation controller and its origin y position starts at 1/3 height of the presentation controller. Of course this is just an example, you can change layout however you wish. Here’s the code for the custom presentation controller:

import UIKit
final class CustomPresentationController: UIPresentationController {
// MARK: – Properties
private lazy var dimmingView: UIView = {
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
dimmingView.alpha = 0.0
let recognizer = UITapGestureRecognizer(target: self,
action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)
return dimmingView
}()
override var frameOfPresentedViewInContainerView: CGRect {
var frame: CGRect = .zero
frame.size = size(forChildContentContainer: presentedViewController,
withParentContainerSize: containerView!.bounds.size)
frame.origin.y = containerView!.frame.height*(1.0/3.0)
return frame
}
override func presentationTransitionWillBegin() {
guard let containerView = containerView else {
return
}
containerView.insertSubview(dimmingView, at: 0)
NSLayoutConstraint.activate([
dimmingView.topAnchor.constraint(equalTo: containerView.topAnchor),
dimmingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
dimmingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
dimmingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 1.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 1.0
})
}
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 0.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0.0
})
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func size(forChildContentContainer container: UIContentContainer,
withParentContainerSize parentSize: CGSize) -> CGSize {
return CGSize(width: parentSize.width, height: parentSize.height*(2.0/3.0))
}
}
// MARK: – Private
private extension CustomPresentationController {
@objc func handleTap(recognizer: UITapGestureRecognizer) {
presentingViewController.dismiss(animated: true)
}
}

And the result:

Leave a comment