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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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:

