【実装MEMO】UITabBarControllerでAnimationを実装するためのヒント

Fumiya Sakai
Dec 31, 2018 · 12 min read

UITabBarControllerでのタブ押下時や表示しているコンテンツを切り替えるにアニメーションを施したい場合の簡単な事例の紹介です。

⭐️ 実装ポイント

まずはUITabBarControllerDelegateで切り替える先のインデックス値を取得するタイミングで、画面を切り替えるためのアニメーションを加える処理を一緒にするようにします。

そして配置しているタブ要素に対してアニメーションを適用する場合には、UITabBarクラスのインスタンスにおいてsubviewsプロパティからUIViewないしはUIImageViewの要素を取得してアニメーションを実行させる形をとります。

  • 切り替える先のインデックスを取得する

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool

  • タブ要素のアイコン画像にアニメーションを適用する

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)

を利用するのがポイントになります。

⭐️ コードでの実装例

下記に簡単なコードでの実装例をまとめています。私が作成する場合はUITabBarControllerを継承したクラスの形をとる形として、Storyboardに配置したUITabBarControllerの画面に対してこのクラスを適用する様にしています。

import UIKitclass GlobalTabBarController: UITabBarController {    // MARK: - Override Function    override func viewDidLoad() {
super.viewDidLoad()

// UITabBarControllerDelegateの宣言
self.delegate = self
// 初期設定として空のUIViewControllerのインスタンスを追加する
self.viewControllers = [UIViewController(), UIViewController(), UIViewController(), UIViewController()]
// ...(以下GlobalTabBarControllerに表示するコンテンツを表示させるための処理を追記する)...
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { // MEMO: UITabBarに配置されているアイコン画像をアニメーションさせるための処理
// 現在配置されているUITabBarからUIImageViewを取得して配列にする
let targetClass: AnyClass = NSClassFromString("UITabBarButton")!
let tabBarViews = tabBar.subviews.filter{ $0.isKind(of: targetClass) }
let tabBarImageViews = tabBarViews.map{ $0.subviews.first as! UIImageView }
// アイコン画像をバウンドさせるようなアニメーションを付与する
UIView.animateKeyframes(withDuration: 0.16, delay: 0.0, options: [.autoreverse], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 1.0, animations: {
tabBarImageViews[item.tag].transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
})
UIView.addKeyframe(withRelativeStartTime: 1.0, relativeDuration: 1.0, animations: {
tabBarImageViews[item.tag].transform = CGAffineTransform.identity
})
})
}
// MARK: - Private Function // タブ選択時に中のコンテンツをスライドさせるアニメーションを付与する
private func animateTabContents(_ toIndex: Int) {
// MEMO: カスタムアニメーションに必要な要素をそれぞれ取得する
guard let tabViewControllers = self.viewControllers else {
return
}
guard let selectedViewController = self.selectedViewController else {
return
}
guard let fromView = selectedViewController.view else {
return
}
guard let toView = tabViewControllers[toIndex].view else {
return
}
// MEMO: タブ切り替え対象のインデックス値を取得し、遷移先と遷移元のインデックス値が同じの場合は以降の処理は実行しない
guard let fromIndex = tabViewControllers.lastIndex(of: selectedViewController) else {
return
}
if fromIndex == toIndex {
return
}
// MEMO: 遷移元のViewの親Viewへ遷移先のViewを追加する
guard let superview = fromView.superview else {
return
}
superview.addSubview(toView)

// MEMO: 左右どちらにスライドするかを決める
let screenWidth = UIScreen.main.bounds.size.width
let shouldScrollRight = toIndex > fromIndex
let offset = (shouldScrollRight ? screenWidth : -screenWidth)
toView.center = CGPoint(x: fromView.center.x + offset, y: toView.center.y)

// アニメーション開始直前にUserInteractionを無効にする
view.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.46, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: { // MEMO: 左右どちらかにスライドするアニメーションを付与する
fromView.center = CGPoint(x: fromView.center.x - offset, y: fromView.center.y)
toView.center = CGPoint(x: toView.center.x - offset, y: toView.center.y)
}, completion: { finished in // 遷移元のViewを削除にしてUserInteractionを有効にする
fromView.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}
}
// MARK: - UITabBarControllerDelegateextension GlobalTabBarController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { // 移動する先のインデックス値を取得する
guard let tabViewControllers = tabBarController.viewControllers else {
return false
}
guard let toIndex = tabViewControllers.lastIndex(of: viewController) else {
return false
}
// コンテンツを切り替えるアニメーションを実行する
animateTabContents(toIndex)
return true
}
}

今回の実装では、遷移先のタブに対応するコンテンツを表示する場合に左右にスライドする様なアニメーションを伴って切り替わる形になります。selectedViewControllerプロパティから遷移元のView要素が取得でき、viewControllers[遷移先のインデックス値]とすると遷移先のView要素を取得することができます。

そして遷移先画面におけるView要素における親Viewへ遷移元のView要素の中に追加し、表示位置を切り替えるようなアニメーションを実装することで実現させるような形です(該当部分の実装に関してのイメージはCustomTransitionに近いかと思います)。

⭐️ 参考資料

今回のアニメーションに関しては割とシンプルなものになりますが、実装の上で参考にした資料は下記になります。

コンテンツ切り替え時のアニメーション実装例:

UITabBarにアニメーションを付与する:

Githubで見つけた実装例はこんな感じになります(※元リポジトリはSwiftのバージョンが低いので最新のSwiftのSyntaxで置き換える必要があります)

Stack Overflowでの情報例:

類似した表現ができるライブラリ例:

コンテンツ表示時の間に関する繋ぎ目となる部分ついても、ほんの少し工夫してみるとなかなか良いものになるかと思います。

Fumiya Sakai

Written by

iOSアプリのUI実装が好きな元デザイナーからジョブチェンジをしたエンジニア。QiitaやGithubなどでもUI実装に関するサンプルや解説記事を投稿したり、たまに登壇しています。こちらでは実装の過程で出てきたメモとかを展開します。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade