0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UIKit でタブバー選択時にアイコンをアニメーションさせる

Posted at

はじめに

UIKit でタブバー選択時にアイコンのアニメーションの実装をすることがあったので、備忘録として記事を書きます!

経緯

元々はタブバーのアニメーションは、以下ライブラリを利用して実装していました。

ただ、storyboad 必須のライブラリで、コードのみの実装できませんでした。
個人的に storyboad はメンテしづらくて好きじゃないので、コードベースでタブバーを実装しようと思うと、animated-tab-barの依存を剥がさざるを得ませんでした。
そこで、タブバーのアニメーションは UIKit で直接実装することにしました。

ゴール

実装した結果はこんな感じです。
タブバーを選択したら、選択されたアイコンがバウンドするようなアニメーションを動かします。

Simulator Screen Recording - iPhone 16 - 2025-04-03 at 22.41.49.gif

実装する

タブバーの ViewController を用意する

まずはタブバーの ViewController クラスを実装します。
今回の例では、以下タブを用意します。

  • Home
  • Chat
  • Graph
  • Setting

実装内容はこちらです。

import UIKit

final class MainTabBarController: UITabBarController {

	// MARK: - lifecycle method

	override func viewDidLoad() {

		super.viewDidLoad()
		setupTab()
	}
}

// MARK: - private method

private extension MainTabBarController {

	func setupTab() {

		guard let mainVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .MainViewController) as? MainViewController,
		      let chatVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .ChatViewController) as? ChatViewController,
		      let graphVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .DispViewController) as? DispViewController,
		      let settingVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .SettingViewController) as? SettingViewController
		else {

			assertionFailure("Not found require viewController.")
			return
		}

		mainVC.tabBarItem = getTabBarItem(title: "Home",
		                                  systemImageName: "homekit",
		                                  tag: 0)
		chatVC.tabBarItem = getTabBarItem(title: "Chat",
		                                  systemImageName: "message.fill",
		                                  tag: 1)
		graphVC.tabBarItem = getTabBarItem(title: "Graph",
		                                   systemImageName: "chart.xyaxis.line",
		                                   tag: 2)
		settingVC.tabBarItem = getTabBarItem(title: "Setting",
		                                     systemImageName: "gearshape.fill",
		                                     tag: 3)

		viewControllers = [mainVC, chatVC, graphVC, settingVC]
	}

	func getTabBarItem(title: String, systemImageName: String, tag: Int) -> UITabBarItem {

		let tabBarItem = UITabBarItem(title: title, image: getTabIcon(systemImageName), tag: tag)
		tabBarItem.selectedImage = getTabIcon(systemImageName)
		return tabBarItem
	}

	func getTabIcon(_ iconName: String) -> UIImage? {

		return UIImage(systemName: iconName)
	}
}

タブバー選択時のアニメーションを実装する

ここからが本題で、タブバー選択時に選択された要素をアニメーションさせていきます。

まずタブバーで選択されたときにアニメーションを行うので、UITabBarControllerDelegateに準拠して、選択されたタイミングを検知できるようにします。
追加したdelegateメソッドのtabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Boolでは、タブバーのいずれかの要素をタップされた瞬間にトリガーされます。

先ほどのコードに、タブバーのいずれかの要素をタップされたときに検知できるコードを追加します。

import UIKit

final class MainTabBarController: UITabBarController {

	// MARK: - lifecycle method

	override func viewDidLoad() {

		super.viewDidLoad()
		setupTab()
+       self.delegate = self
	}
}

+ extension MainTabBarController: UITabBarControllerDelegate {
+ 
+ 	func tabBarController(_ tabBarController: UITabBarController,
+ 	                      shouldSelect viewController: UIViewController) -> Bool {
+ 
+ 		return true
+ 	}
+ }

続いて、タブバーで選択された要素をアニメーションさせる処理を追加していきます。

extension MainTabBarController: UITabBarControllerDelegate {

	func tabBarController(_ tabBarController: UITabBarController,
	                      shouldSelect viewController: UIViewController) -> Bool {
        
+       animateTabItem(tabBarController, shouldSelect: viewController)
		return true
	}

+   private func animateTabItem(_ tabBarController: UITabBarController,
+                               shouldSelect viewController: UIViewController) {
+       guard let item = viewController.tabBarItem,
+             let index = tabBarController.tabBar.items?.firstIndex(of: item),
+             let targetTabBarView = getBarItemView(tabBar: tabBarController.tabBar,
+                                                   selectedIndex: index + 1)
+       else {
+           return
+       }
+       
+       bounceAnimation(target: targetTabBarView)
+   }
+
+	private func getBarItemView(tabBar: UITabBar, selectedIndex: Int) -> UIView? {
+
+		let tabBarSubviews = tabBar.subviews.sorted(by: { $0.frame.minX < $1.frame.minX })
+		guard tabBarSubviews.indices.contains(selectedIndex) else { return nil }
+		return tabBarSubviews[selectedIndex]
+	}
+
+	private func bounceAnimation(target view: UIView) {
+
+		UIView.animate(withDuration: 0.1) {
+
+			view.transform = .init(scaleX: 1.2, y: 1.2)
+		} completion: {
+
+			guard $0 else { return }
+			UIView.animate(withDuration: 0.1) {
+
+				view.transform = .init(scaleX: 1, y: 1)
+			}
+		}
+	}
}

animateTabItemメソッドで選択されたタブバーのボタンをアニメーションさせる処理を行います。
具体的にやっていることはまず、アニメーションしたいtabBarItemをUIView型として取得します。

以下処理で、選択されたタブの画面(viewController)からtabBarItemを取得して、選択された画面のタブバーからみたときのインデックスを取得しています。

        guard let item = viewController.tabBarItem,
	          let index = tabBarController.tabBar.items?.firstIndex(of: item),
	          let targetTabBarView = getBarItemView(tabBar: tabBarController.tabBar,
		                                            selectedIndex: index + 1)
		else {
			return
		}

getBarItemViewメソッドでは、選択されたインデックスを使ってタブバーのsubviewsから選択されたタブのUIView型で取得します。

private func getBarItemView(tabBar: UITabBar, selectedIndex: Int) -> UIView? {

		let tabBarSubviews = tabBar.subviews.sorted(by: { $0.frame.minX < $1.frame.minX })
		guard tabBarSubviews.indices.contains(selectedIndex) else { return nil }
		return tabBarSubviews[selectedIndex]
	}

タブバーのsubviewsを取得するときに、frameをみてタブの左から右の順に並び替えていますが(画面でタブを見た時と同じ並びにする)
これを行っているのは、こちらの記事に記載されている通り時々subviewsでとれるviewの並びが画面通りの並びになっておらず選択したタブのインデックスと異なるタブが取れてしまうことがあるため、並び替えています。

let tabBarSubviews = tabBar.subviews.sorted(by: { $0.frame.minX < $1.frame.minX })

上記流れで、アニメーションさせたいUIViewが取得できたので、bounceAnimationでアニメーションさせる実装を追加します。

private func bounceAnimation(target view: UIView) {

		UIView.animate(withDuration: 0.1) {

			view.transform = .init(scaleX: 1.2, y: 1.2)
		} completion: {

			guard $0 else { return }
			UIView.animate(withDuration: 0.1) {

				view.transform = .init(scaleX: 1, y: 1)
			}
		}
	}

アニメーションの実装はシンプルで、
0.1秒間で選択したタブを1.2倍に膨らませて、その後0.1秒間で元の大きさに戻すようにアニメーションをしています。

最終的な実装が以下の通りです。

final class MainTabBarController: UITabBarController {

	// MARK: - factory method

	static func build() -> UIViewController {

		let vc = MainTabBarController()
		return vc
	}

	// MARK: - lifecycle method

	override func viewDidLoad() {

		super.viewDidLoad()
		self.delegate = self
		setupTab()
	}
}

extension MainTabBarController: UITabBarControllerDelegate {

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

		animateTabItem(tabBarController, shouldSelect: viewController)
		return true
	}

	private func animateTabItem(_ tabBarController: UITabBarController,
	                            shouldSelect viewController: UIViewController) {
		guard let item = viewController.tabBarItem,
		      let index = tabBarController.tabBar.items?.firstIndex(of: item),
		      let targetTabBarView = getBarItemView(tabBar: tabBarController.tabBar,
		                                            selectedIndex: index + 1)
		else {
			return
		}

		bounceAnimation(target: targetTabBarView)
	}

	private func getBarItemView(tabBar: UITabBar, selectedIndex: Int) -> UIView? {

		let tabBarSubviews = tabBar.subviews.sorted(by: { $0.frame.minX < $1.frame.minX })
		guard tabBarSubviews.indices.contains(selectedIndex) else { return nil }
		return tabBarSubviews[selectedIndex]
	}

	private func bounceAnimation(target view: UIView) {

		UIView.animate(withDuration: 0.1) {

			view.transform = .init(scaleX: 1.2, y: 1.2)
		} completion: {

			guard $0 else { return }
			UIView.animate(withDuration: 0.1) {

				view.transform = .init(scaleX: 1, y: 1)
			}
		}
	}
}

// MARK: - private method

private extension MainTabBarController {

	func setupTab() {

		guard let mainVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .MainViewController) as? MainViewController,
		      let chatVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .ChatViewController) as? ChatViewController,
		      let graphVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .DispViewController) as? DispViewController,
		      let settingVC = UIStoryboard(custom: .Main).instantiateViewController(custom: .SettingViewController) as? SettingViewController
		else {

			assertionFailure("Not found require viewController.")
			return
		}

		mainVC.tabBarItem = getTabBarItem(title: "Home",
		                                  systemImageName: "homekit",
		                                  tag: 0)
		chatVC.tabBarItem = getTabBarItem(title: "Chat",
		                                  systemImageName: "message.fill",
		                                  tag: 1)
		graphVC.tabBarItem = getTabBarItem(title: "Graph",
		                                   systemImageName: "chart.xyaxis.line",
		                                   tag: 2)
		settingVC.tabBarItem = getTabBarItem(title: "Setting",
		                                     systemImageName: "gearshape.fill",
		                                     tag: 3)

		viewControllers = [mainVC, chatVC, graphVC, settingVC]
	}

	func getTabBarItem(title: String, systemImageName: String, tag: Int) -> UITabBarItem {

		let tabBarItem = UITabBarItem(title: title, image: getTabIcon(systemImageName), tag: tag)
		tabBarItem.selectedImage = getTabIcon(systemImageName)
		return tabBarItem
	}

	func getTabIcon(_ iconName: String) -> UIImage? {

		return UIImage(systemName: iconName)
	}
}

以上で、目標のタブバー選択時のタブのアニメーションの実装が完了です!

終わり

タブバーのアニメーションの実装例を紹介しましたが、他にももっと良い実装があると思うので、ご指摘などありましたらぜひコメントいただけると幸いです🙏

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?