Posted at

【Swift】UIPresentationControllerを使ってモーダルビューを表示する

More than 1 year has passed since last update.


1.はじめに

 まずは完成形を。

 遷移元の画面上にオーバレイする形でモーダルビューを表示します。

 いつもとちょっと違ったモーダルを出したいときに重宝しています。

modal.gif


2.呼び元の ViewController

class PresentationControllerViewController: UIViewController {

@IBAction func openButton(_ sender: UIButton) {
let modalViewController = ModalViewController()
modalViewController.modalPresentationStyle = .custom
modalViewController.transitioningDelegate = self
present(modalViewController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

extension PresentationControllerViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return CustomPresentationController(presentedViewController: presented, presenting: presenting)
}
}

openButton(_ sender: UIButton) はstoryboardで配置したボタンと接続します。モーダルビューを開くための機能をここに実装します。

ポイントは modalViewController.modalPresentationStyle.custom をセットすることです。

UIViewControllerTransitioningDelegate プロトコルに準拠し、これから作る CustomPresentationController を返すようにします。


3.モーダルビューを乗せる UIPresentationController

margin の値を変えることでモーダルビューの大きさを変えることができます。

class CustomPresentationController: UIPresentationController {

// 呼び出し元のView Controller の上に重ねるオーバレイView
var overlayView = UIView()

// 表示トランジション開始前に呼ばれる
override func presentationTransitionWillBegin() {
guard let containerView = containerView else {
return
}
overlayView.frame = containerView.bounds
overlayView.gestureRecognizers = [UITapGestureRecognizer(target: self, action: #selector(CustomPresentationController.overlayViewDidTouch(_:)))]
overlayView.backgroundColor = .black
overlayView.alpha = 0.0
containerView.insertSubview(overlayView, at: 0)

// トランジションを実行
presentedViewController.transitionCoordinator?.animate(alongsideTransition: {[weak self] context in
self?.overlayView.alpha = 0.7
}, completion:nil)
}

// 非表示トランジション開始前に呼ばれる
override func dismissalTransitionWillBegin() {
presentedViewController.transitionCoordinator?.animate(alongsideTransition: {[weak self] context in
self?.overlayView.alpha = 0.0
}, completion:nil)
}

// 非表示トランジション開始後に呼ばれる
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
overlayView.removeFromSuperview()
}
}

let margin = (x: CGFloat(30), y: CGFloat(220.0))
// 子のコンテナサイズを返す
override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
return CGSize(width: parentSize.width - margin.x, height: parentSize.height - margin.y)
}

// 呼び出し先のView Controllerのframeを返す
override var frameOfPresentedViewInContainerView: CGRect {
var presentedViewFrame = CGRect()
let containerBounds = containerView!.bounds
let childContentSize = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerBounds.size)
presentedViewFrame.size = childContentSize
presentedViewFrame.origin.x = margin.x / 2.0
presentedViewFrame.origin.y = margin.y / 2.0

return presentedViewFrame
}

// レイアウト開始前に呼ばれる
override func containerViewWillLayoutSubviews() {
overlayView.frame = containerView!.bounds
presentedView?.frame = frameOfPresentedViewInContainerView
presentedView?.layer.cornerRadius = 10
presentedView?.clipsToBounds = true
}

// レイアウト開始後に呼ばれる
override func containerViewDidLayoutSubviews() {
}

// overlayViewをタップした時に呼ばれる
@objc func overlayViewDidTouch(_ sender: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
}