iOSでの半モーダル/ハーフモーダルの実装についてのまとめ


はじめに

  • iOSのUIでハーフモーダルを見かけることが増えました。Apple純正のアプリでも実装されることが多く、今後実装することが多くなると思います。 今後の実装の参考になるように情報をまとめたいと思いました。

半モーダル/ハーフモーダルとは

・モードの完全遷移が起こらない
・モードを多重化しながらパラレルに対話可能
・モードを終了させずにモードからの一時退避が可能
・擬似的なマルチウインドウ・インターフェイスに応用可能
・スワイプなどインタラクションコストの低い操作方法によってモードの切り替えが可能
  • Pull dissmissや画面遷移せずにタスクを切り替える機能などによく使われます
  • 代表的なものはAppleのMapアプリです

 実装方法

UIModalPresentationStyleを使う

     let vc = UIViewController()
     vc.modalPresentationStyle = .pageSheet
     present(vc, animated: true, completion: nil)
  • この実装だと下階層の画面に対してタップできないなどの仕様の制限があります。

ライブラリを使う

  • すでに完成度の高いライブラリが存在するので実装コストを下げることができます。

SCENEE/FloatingPanel

  • Apple純正のアプリのような動きを再現できます

slackhq/PanModal

自前で実装する

  • より完成度の高い完成度を目指す場合は自前で実装することも選択肢に入ります。実装する上でのポイントをあげておきます。

徹底調査

  • 完成度の高いライブラリが多いので中身を覗いてみると理解が進みます。とくに上記で挙げたライブラリは参考になります。
  • cocoacontrolsなどを使うとたくさんのライブラリを見つけることができます。

UIViewControllerTransitioningDelegate

  • この実装をすることで表示のアニメーションのカスタマイズと表示後の画面もカスタマイズできます
let vc = UIViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
present(vc, animated: true, completion: nil)
extension UIViewController: UIViewControllerTransitioningDelegate {
    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return UIPresentationController(presentedViewController: presented, presenting: presenting)
    }

    public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentationAnimator()
    }

    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissionAnimator()
    }
}

PSPDFTouchForwardingView

final class PSPDFTouchForwardingView: UIView {

    final var passthroughViews: [UIView] = []

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let hitView = super.hitTest(point, with: event) else { return nil }
        guard hitView == self else { return hitView }

        for passthroughView in passthroughViews {
            let point = convert(point, to: passthroughView)
            if let passthroughHitView = passthroughView.hitTest(point, with: event) {
                return passthroughHitView
            }
        }

        return self
    }
}
private var touchForwardingView: PSPDFTouchForwardingView?
override func presentationTransitionWillBegin() {
    super.presentationTransitionWillBegin()
    touchForwardingView = PSPDFTouchForwardingView(frame: containerView!.bounds)
    containerView?.insertSubview(touchForwardingView, at: 0)
}

まとめ

  • 近年ハーフモーダルのUIを採用するアプリが増えてきている
  • 完成度の高いライブラリも多く実装コストを下げることができる
  • 自前実装もポイントを押さてしまえば簡単です🙆‍♂️

参考リンク