ユウのブログ

なんでも書きますよ。

【Swift5.x】MainTabBarを切り替えた時にシュッとアニメーションさせる

はじめに

タイトル通り。MainTabBarを切り替えた時にシュッとアニメーションさせると見栄えするし、アプリ側でタブ切り替えを行う時にユーザーにそれが伝わりやすい。ぶっちゃけNetflixのパクリ

ソースコード

github.com  

ソースコードは上のリポジトリに置いてあります。

 

ビルド結果

上のソースコードをビルドするとこんな感じ。ビューのデザインとかは新しいプロジェクトを作ったときのデフォルトのやつです(作るのがめんどくさかった)。

 

1/10倍速。動画をスロー再生してるわけじゃなくて、animateメソッドのwithDurationを10倍にしてビルドしたやつを録画してます。 

中身の話

正直ソースコードを貼り付けた時点で書くことがなにもないんですが、一応色々書いていこうと思います。 

delegateメソッド

UITabBarControllerDelegateのtabBarController(_:animationControllerForTransitionFrom:to:)メソッドを使います。  

func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    guard
        let viewControllers = tabBarController.viewControllers,
        let fromIndex = viewControllers.firstIndex(of: fromVC),
        let toIndex = viewControllers.firstIndex(of: toVC)
    else { return nil }

    let scrollRight = toIndex > fromIndex

    return TabBarAnimatedTransitioning(scrollRight)
}

tabBarController(_animationControllerForTransitionFrom_to) - UITabBarControllerDelegate _ Apple Developer Documentation

  このメソッドの返り値に指定したUIViewControllerAnimatedTransitioningオブジェクトのanimateTransitionメソッドを使っていい感じにアニメーションしてくれます。fromVC、toVCが遷移元のviewControllerと遷移先のviewControllerになるので、これを使ってアニメーションを作ります。

  このメソッドを使わなくても、タブバーのタップを検出するメソッドは他にもあるので、自前のメソッドでアニメーションを再生することもできるんですが、特別な理由がない限り既存のSDKでできる事を再開発する必要もないと思います。 

第3のビューがアニメーションに登場するとかならハックする必要があるかもしれません(そんなシチュエーションあるか?)。

  横向きにスクロールするということで、scrollRightくんを生成します。彼がtrueなら右スクロール、falseなら左スクロールという感じですね。この値はviewControllersのindexを使って決めます。

余談ですが、最近のiOSではarray.index(:)ではなくarray.firstIndex(:)を使うようになっています。まぁビルドターゲットのバージョンに合わせてXcodeくんが添削してくれるので、あんまり意識することはないと思いますが。

あんまりないとは思いますが、上下とかにもスクロールするシチュエーションがある場合、enumを使ったほうがよさそうです。左右ならわざわざenumを書くほどでもない気がする(書いたほうがわかりやすい気もする)。

transitionアニメーションの生成

UITabBarControllerDelegateのtabBarController(_:animationControllerForTransitionFrom:to:)メソッドの返り値にTabBarAnimatedTransitioning(_: Bool)メソッドを初期化して渡しています。

このメソッドの名前は何でもいいので、プロジェクトに合わせてかっこいいネーミングをしていくといい感じになります。

NSObjectとUIViewControllerAnimatedTransitioningを継承させます。NSObjectProtocolに準拠しているプロトコルなので、NSObjectを継承していないとまぁまぁめんどくさい感じになります(色々stubしろよって言われる)。

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard
            let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from),
            let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
        else { return }
        
        transitionContext.containerView.addSubview(toView)
        
        let screenWidth = UIScreen.main.bounds.size.width / 4
        let offset = (scrollRight ? screenWidth : -screenWidth)
        toView.alpha = 0
        toView.center.x += offset
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseInOut], animations: {
            
            fromView.alpha = 0
            fromView.center.x = fromView.center.x - offset
            toView.alpha = 1
            toView.center.x = toView.center.x - offset
        }, completion: {
            transitionContext.completeTransition($0)
        })
    }

UIViewControllerAnimatedTransitioning - UIKit _ Apple Developer Documentation

animateTransitionメソッドの中でanimateメソッドを使ってアニメーションさせます。

animateTransition(using_) - UIViewControllerAnimatedTransitioning _ Apple Developer Documentation

UIKitはViewControllerを表示または非表示する際にこのメソッドを呼び出す、とあるので、手作業でどこでメソッドを実行して〜みたいなコードを書く必要はないです。

UIScreenの横幅を取得して1/4にしたものをoffsetとして、遷移先ビューの初期値をoffsetぶん横にずらし、alphaを0にセットします。あとはanimateメソッドの中で遷移元ビューのalphaを0にしつつoffsetぶん横にずらしながら、同時に遷移元ビューのalphaを1にしつつ中央に戻します。

アニメーションの再生が終わったらcompletionが呼ばれるので、transitionContextにトランジションが終わったことを通知します。fromViewのremoveFromSuperView()なんかは書かなくても通知した時に勝手にやってくれるみたいです。

animateメソッドを使わない場合、UIKitはトランジションが終了した際にanimationEnded(_:)メソッドを呼び出すらしいので、こちらでtransitionContextへの通知を行う感じになるでしょうか。たぶんなると思います(試してない)。

animationEnded(__) - UIViewControllerAnimatedTransitioning _ Apple Developer Documentation

MainTabBarControllerでの処理

ビューのalphaをいじってビューが消えているっぽく見せているので、viewDidLoad()でビューのbackgroundColorをセットしておきます。

    override func viewDidLoad() {
        
        super.viewDidLoad()
        self.delegate = self
        view.backgroundColor = UIColor.systemBackground
    }

iOS13か12あたりから.systemBackgroundが使えます。OSの設定がダークモードなら黒、ライトモードなら白になります。

それ未満のバージョンに対応する場合は@availableを使って分岐させるのが手っ取り早いと思います。.systemBackgroundを使う時に、プロジェクトのビルドターゲットに応じてXcodeがエラーを吐いてくれます。たぶん。

アプリのイメージカラーにもよりますが、だいたいは白か黒にしておけば間違いないと思います。デザイナーさんと相談しましょう。

参考ページ

記事中でも紹介したAppleのドキュメントたち

tabBarController(_animationControllerForTransitionFrom_to) - UITabBarControllerDelegate _ Apple Developer Documentation

UIViewControllerAnimatedTransitioning - UIKit _ Apple Developer Documentation

animateTransition(using_) - UIViewControllerAnimatedTransitioning _ Apple Developer Documentation

animationEnded(__) - UIViewControllerAnimatedTransitioning _ Apple Developer Documentation

それ以外の参考にしたページ

Making Tab Bar Slide When Selected _ @samwize

iOS Dev Course_ UITabBarController Animated Transitioning

終わりに

やばいな。もう書くことがない。

最初の頃は結構迷走したりしてたんですが、つよいエンジニアに色々教えてもらってわりといい感じにまとまったんじゃないかと思います。

ざっくりしたことしか書いてないですが、何かしらの参考になると嬉しいです。

変なこと書いてるぞボケェ!って思ったらTwitterで教えて下さい。コメントは見ないわけじゃないけどあんまり見ないので反応が遅れます。

Twitter: @yuhachi0220