swiftの画面遷移で画面を上から下に表示する方法

はじめに

やりたかったことは画面上部のメニューバーに設定ボタンをおいてそれをタップすると、画面上からメニューが落ちてくるような動きでした。

単純に、presentViewControllerを呼び出す前にviewController.modalTransitionStyleに適当なスタイルを設定すればできるだろうくらいに軽く考えていたのですが、、、デフォルトで用意されている以外のアニメーションを設定する場合は自作しないといけないみたいでした

var viewController: MenuViewController = MenuViewController()
viewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical // ★ここを適当な値にすればいけると思ってた
self.presentViewController(viewController, animated: true, completion: nil)

実装方法

必要な作業としては、
・アニメーションの動きを記述するクラスの実装
・遷移元のViewControllerでUIViewControllerTransitioningDelegateプロトコルを実装
以下のコードだとanimationControllerForPresentedController、animationControllerForDismissedControllerの箇所です

ViewController.swift

class ViewController: UIViewController, UIViewControllerTransitioningDelegate {

    // カスタムアニメーション
    var animationController = MenuAnimationController()

・・・

    // メニューを表示します
    func showMenu() {
        var viewController: MenuViewController = MenuViewController()
        viewController.transitioningDelegate = self
        self.presentViewController(viewController, animated: true, completion: nil)
    }

    // ページを開くときに呼ばれます
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animationController.isClose = false
        return animationController
    }
    
    // ページを閉じるときに呼ばれます
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animationController.isClose = true
        return animationController
    }
}

MenuAnimationController.swift

class MenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    
    // 閉じる場合はtrueをセット
    var isClose: Bool!
    
    // アニメーションにかかる時間をセット
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.3
    }
    
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        // 遷移元、遷移先ViewController
        var toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
        var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
        
        var containerView = transitionContext.containerView()
        var screenBounds = UIScreen.mainScreen().bounds

        // アニメーション終了時のframe
        var finalFrame = transitionContext.finalFrameForViewController(toViewController)
        
        // アニメーション開始時のframe(上からなのでマイナス値)
        var menuStartFrame = CGRectOffset(finalFrame, 0, screenBounds.size.height * -1)
        
        // メニューを開くアニメーション
        if !isClose {
            toViewController.view.frame = menuStartFrame
            containerView.addSubview(toViewController.view)
            UIView.animateWithDuration(
                transitionDuration(transitionContext),
                animations: {
                    toViewController.view.frame = finalFrame
                },
                completion: {
                    finished in
                    transitionContext.completeTransition(true)
                }
            )
        // メニュー閉じるアニメーション
        } else {
            containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
            UIView.animateWithDuration(
                transitionDuration(transitionContext),
                animations: {
                    fromViewController.view.frame = menuStartFrame
                },
                completion: {
                    finished in
                    transitionContext.completeTransition(true)
                }
            )
        }
    }
}

さいごに

やりかたとか全然しらなかったとはいえ実装するのに丸1日くらいかかりました、、、
うーん、難しい