kitoko552.memo

kitoko552のメモ

UIViewControllerAnimatedTransitioningを使って自作の画面遷移を実装する【基本編】

要点

  • iOSでは自作の画面遷移をCustom SegueやUIViewControllerAnimatedTransitioningを使って実装できる。
  • UIViewControllerAnimatedTransitioningを使った実装方法では、UIViewControllerAnimatedTransitioningに準拠したクラスの実装と遷移先のUIViewControllerでのUIViewControllerTransitioningDelegateのメソッドの実装の2つが必要
  • それぞれの雛形は以下
  • 具体例編へ続く...
class Animator: NSObject {
    let duration: NSTimeInterval
    // 他プロパティやイニシャライザ
}

extension Animator: UIViewControllerAnimatedTransitioning {
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        guard let containerView = transitionContext.containerView(),
            fromView = transitionContext.viewForKey(UITransitionContextFromViewKey),
            toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
            return
        }

        // アニメーションの処理
    }
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        transitioningDelegate = self
    }
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentAnimator()
    }
    
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }
}

iOSアプリの画面遷移

iOSで標準で実装されている遷移は大きく分けて、PushとModalの2通りあります。
しかし、この2通りの画面遷移ではアプリの表現の幅が減ってしまいます。
そのため、iOSではPushとModalだけでなく自作した画面遷移を実装することが可能になっています。
この記事ではそういった自作の画面遷移を実装する方法について書いていきます。

自作の画面遷移を実装する方法

自作の画面遷移を実装する方法は以下の2つです。
1. Custom Segueを使った方法 - UIViewControllerAnimatedTransitioningを使った方法

1のCustom Segueを使った方法については僕もよく知らないのでこの記事では書きません。
ググってください。

この記事で紹介するのは2のUIViewControllerAnimatedTransitioningを使った方法です。

UIViewControllerAnimatedTransitioningの公式Referenceはこちら
developer.apple.com

結論から言うと、UIViewControllerAnimatedTransitioningを使った画面遷移の実装では、UIViewControllerAnimatedTransitioningに準拠したクラスの実装と遷移先のUIViewControllerでのUIViewControllerTransitioningDelegateのメソッドの実装の2つが必要です。

1つずつ説明していきます。

UIViewControllerAnimatedTransitioningに準拠したクラスの実装

まずはUIViewControllerAnimatedTransitioningに準拠したクラスの実装です。

まず、UIViewControllerAnimatedTransitioningはprotocolです。
こいつの宣言部分を見てみると、以下のようになっています(コメントは省いています)。

public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {
    public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval
    public func animateTransition(transitionContext: UIViewControllerContextTransitioning)    
    optional public func animationEnded(transitionCompleted: Bool)
}

上からわかるのが、まずUIViewControllerAnimatedTransitioningはNSObjectProtocolに準拠しているため、NSObjectを継承したクラスでなければUIViewControllerAnimatedTransitioningに準拠することはできません。
これに注意しましょう。

そしてUIViewControllerAnimatedTransitioningに準拠したクラスはtransitionDuration()animateTransition()の2つのメソッドを実装しなければなりません。
こいつらが画面遷移の実装の鍵となります。
特にanimateTransition()は画面遷移の挙動を書くことになるので一番の鍵となるメソッドと言ってもいいでしょう。

この2つのメソッドについて詳しく説明していきます。

transitionDurationメソッド

このメソッドは画面遷移のアニメーションのdurationを返すメソッドです。
そのため、実装する内容としては目的とする画面遷移のduration(NSTimeInterval)をreturnすればいいでしょう。

基本的には、プロパティでdurationを持つなどしてそれをreturnするだけになると思います。

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return duration
}

animateTransitionメソッド

このメソッドは実際に画面遷移が行われる際に呼ばれるメソッドです。
つまりこのメソッド内の処理が主な画面遷移の実装部分となります。

どのように画面遷移を実装するかですが、基本的には引数にあるtransitionContext: UIViewControllerContextTransitioningを使います。
こいつのviewForKeyメソッドを使えば遷移元のViewと遷移先のViewを取得することができます。
また、containerViewメソッドでこれらのViewのsuperViewにあたるViewを取得することができます。
これらを駆使して、Viewをアニメーションで移動させたりすることで画面遷移を実装していきます。

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    guard let containerView = transitionContext.containerView(),
        fromView = transitionContext.viewForKey(UITransitionContextFromViewKey),
        toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
            return
    }

    // アニメーションの処理
}

1つ目のまとめ

まとめると、UIViewControllerAnimatedTransitioningに準拠したクラスの実装は以下のようになります。

class Animator: NSObject {
    let duration: NSTimeInterval
    // 他プロパティやイニシャライザ
}

extension Animator: UIViewControllerAnimatedTransitioning {
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        guard let containerView = transitionContext.containerView(),
            fromView = transitionContext.viewForKey(UITransitionContextFromViewKey),
            toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
            return
        }

        // アニメーションの処理
    }
}

カスタマイズのしようはありますが、基本的にはこれが雛形となります。

UIViewControllerにUIViewControllerAnimatedTransitioningを準拠させて直接画面遷移を書くこともできますが、ViewControllerが肥大化することやクラスの役割の明確化を考えると新しいクラスを作った方がいいでしょう。

これで1つ目のUIViewControllerAnimatedTransitioningに準拠したクラスの実装は完成です。

UIViewControllerTransitioningDelegateのメソッドの実装

次にUIViewControllerTransitioningDelegateのメソッドの実装です。
実装するのは遷移先のViewControllerであることに注意しましょう。

UIViewControllerはtransitioningDelegateという変数を持っています。
こいつにselfを入れてやってUIViewControllerTransitioningDelegateのメソッドが走るようにしましょう(UIViewControllerに書くのが嫌かもしれませんが、便宜上UIViewControllerに書きます)。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        transitioningDelegate = self
    }
}

UIViewControllerTransitioningDelegateの宣言は以下のようになっています(@available省略)。

public protocol UIViewControllerTransitioningDelegate : NSObjectProtocol {    
    optional public func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?    
    optional public func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
    optional public func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
    optional public func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
    optional public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
}

上のようにデリゲートメソッドが5つありますが、基本的に使うのはanimationControllerForPresentedControlleranimationControllerForDismissedControllerです。

animationControllerForPresentedControllerメソッド

このメソッドでは、present時の画面遷移のアニメーションが実装されているUIViewControllerAnimatedTransitioningを返します。
nilを返した場合は普通のModalでの画面遷移になります。

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return PresentAnimator()
}

animationControllerForDismissedControllerメソッド

このメソッドはanimationControllerForPresentedControllerメソッドの逆で、dismiss時の画面遷移のアニメーションが実装されているUIViewControllerAnimatedTransitioningを返します。
nilを返した場合は普通のModalのdismissになります。

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return DismissAnimator()
}

2つ目まとめ

まとめると、遷移先のUIViewControllerに対して以下のような実装をします。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        transitioningDelegate = self
    }
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentAnimator()
    }
    
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }
}

基本的にはこれが雛形になります。
遷移元のViewControllerは、Modalの画面遷移をするときと同じように(コードでもSegueでも可)実装すれば大丈夫です。

class BaseViewController: UIViewController {
    @IBAction func buttonTapped(sender: UIButton) {
        let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
        let viewController = storyboard.instantiateViewControllerWithIdentifier("Second")
        presentViewController(viewController, animated: true, completion: nil)
    }
}

これでUIViewControllerTransitioningDelegateのメソッドの実装は完成です。

以上の2つの実装で、自作の画面遷移は実現できます。

具体例編へ続く

この記事ではどうやったら自作の画面遷移が実装できるかを説明しました。
しかし、これだけだと抽象的すぎてよくわからないという方も多いと思います。
なので具体例を、「UIViewControllerAnimatedTransitioningを使って自作の画面遷移を実装する【具体例編】(Coming soon)」で書きたいと思います。

そちらも是非ご覧ください。