遷移元のビューが透過で見えるモーダルビュー(UIPresentationControllerを使わないで、既存の画面遷移をいじる感じで実装してみました)

1 はじめに

iOS 8以降で利用可能な、UIPresentationControllerを使用すると、ViewControllerのモーダルのような画面を自由に表示することが可能です。
[iOS 8] UIPresentationController でカスタムのモーダル表示を実装する

今回は、あえて、UIPresentationControllerを使用せず、通常の画面遷移の実装を修正していくことで、このようなバックを透過させたモーダルビューを幾つか試してみたいと思います。

題材としては、次のようなUICollectionViewから選択した画像を、モーダルビューで拡大表示するというような、簡単なサンプルを用意しました。

2 半透明

バックを半透明にして、遷移元のビューが見えるような表現と言うことで、とりあえず、遷移先のビューのbackgroundColorのalpha値を変更します。

override func viewDidLoad() {
  // ・・・省略・・・
  view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
}

しかし、これだけでは、遷移した瞬間はいいのですが、その後、背後になったビューが消えてしまい、真っ黒になってしまいます。

003 002

この問題は、遷移先のビューの Presentationover Current Contentにすることで、解決できます。

001

もし、これをコードで書くなら、遷移前に指定する必要があるので、遷移元のprepare(for:sender:)で設定する必要がります。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let dist = segue.destination as? PresentationViewController {
    dist.modalPresentationStyle = .overCurrentContext
  }
}

動作している様子は、次のとおりです。

3 ぼかし

iOS 8以降で使用可能な、UIVisualEffectViewを使用すると、ぼかし効果を簡単に実装できます。
[iOS 8] ぼかし効果をたった 4 行で!

下記では、遷移先のビューにUIVisualEffectViewを挿入しています。

override func viewDidLoad() {

    // ・・・省略・・・

    // 元のバックのビューは、とりあえず透明にして見えなくする
    view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)

    // UIVisualEffectViewを生成する 
    let visualEffectView = UIVisualEffectView(frame: view.frame)
    // エフェクトの種類を設定
    visualEffectView.effect = UIBlurEffect(style: .regular)
    // UIVisualEffectViewを他のビューの下に挿入する 
    view.insertSubview(visualEffectView, at: 0)

}

UIVisualEffectViewに指定できるエフェクトの種類は、下記の3種類です。

light dark extralight
004 005 006

動作している様子は、次のとおりです。

4 遷移のアニメーション

ここまでの遷移は、デフォルトで準備されているTrasitionから選択するしかありません。

Cover Vertical Flip Horizontal Cross Dissolve
007 008 009

上記以外の遷移を表現するためには、下記の実装が必要です。

  1. 遷移を表現するクラスを作成
  2. 1.を指定するdelegateクラスを作成
  3. 2.をmodalPresentationStyleに設定する

1.は、UIViewControllerAnimatedTransitioningプロトコルを実装したクラスです。 また、2.は、UIViewControllerTransitioningDelegateプロトコルを実装したクラスです。

サンプルでは、1. 及び、2.を1つのクラス(OrgTransition)で実装しています。

class OrgTransition: NSObject, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning{

    fileprivate var isPresent = false

    // MARK: - UIViewControllerTransitioningDelegate
    public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 遷移時にTrasitionを担当する(UIViewControllerAnimatedTransitioningプロトコルを実装した)クラスを返す
        isPresent = true
        return self
    }

    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 復帰時にTrasitionを担当する(UIViewControllerAnimatedTransitioningプロトコルを実装した)クラスを返す
        isPresent = false
        return self
    }

    // MARK: - UIViewControllerAnimatedTransitioning
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.7
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        if isPresent {
            animatePresentTransition(transitionContext: transitionContext)
        } else {
            animateDissmissalTransition(transitionContext: transitionContext)
        }
    }

    // 遷移時のTrastion処理
    func animatePresentTransition(transitionContext: UIViewControllerContextTransitioning) {

        UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {
            // 遷移のアニメーションなど
        }, completion: {
            finished in
            transitionContext.completeTransition(true)
        })
    }

    // 復帰時のTrastion処理
    func animateDissmissalTransition(transitionContext: UIViewControllerContextTransitioning) {
        UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {
            // 遷移のアニメーションなど
        }, completion: {
            finished in
            transitionContext.completeTransition(true)
        })
    }
}

transitioningDelegateに設定するのは、modalPresentationStyleと同様に、遷移前に設定する必要が有ります。

let orgTrasition = OrgTransition() // UIViewControllerTransitioningDelegateを実装したクラス

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let dist = segue.destination as? PresentationViewController {

        // ・・・省略・・・

        dist.transitioningDelegate = orgTrasition
    }
}

なお、UIVisualEffectViewの挿入などは、遷移前に行う必要があるため、こちら側(UIViewControllerAnimatedTransitioning)で実装する必要があります。詳しくは、サンプルをご参照下さい。

5 最後に

今回は、前の画面を残す遷移について、既存の画面遷移をいじる感じで、色々試してみました。 これらを把握すれば、かなり自由に画面遷移が表現できるのでは無いでしょうか。

サンプルは下記に置いています。気になるところが有りましたら、ぜひ教えてやってください。
github [GitHub] https://github.com/furuya02/PresentationViewSample

6 参考リンク


[iOS 8] ぼかし効果をたった 4 行で!
[iOS 8] UIPresentationController でカスタムのモーダル表示を実装する