ContainerViewController / Custom TabBarControllerを作る
TabBarをカスタマイズしたい
標準のUITabBarControllerだと、痒いところをカスタマイズするのが大変です。 TabBarをUIViewのサブクラスとして自作することで、自由にUIやアニメーションの実装ができるようになると思います。 UITabBarController - UIKit | Apple Developer Documentation
TabViewControllerの挙動
iOS5からContainerViewControllerを作成するAPIが公開されたようで、それを使ってTabViewControllerを自作していきます。
TabViewControllerの挙動を整理しておくと、以下のようにタブ切り替えのUIを実現しつつ適切なタイミングでライフサイクルメソッドが呼ばれることになります。
- タブをタップすると画面が切り替わる
切り替わる際は以下の順でライフサイクルイベントが発火する
- 遷移先ViewControllerのViewDidDisappear
- 遷移元ViewControllerのviewWillDisappear
- 遷移元ViewControllerのviewDidDisappear
- 遷移先ViewControllerのDidAppear
PushやModalで遷移すると、TabViewControllerと選択されているViewControllerのViewDidAppearが呼ばれる
- 同一タブを選択しても何も起こらない。
必要なメソッド
以下の3種類のUIViewControllerのメソッドを使うことで実現することができます。
- willMove / didMove
- addChild / removeFromParent
- begeinAppearanceTransition / endAppearenceTransition
実装例
ViewControllerにTabViewとContentViewを配置します。
final class ContainerTapViewController: UIViewController { private let contentView = UIView() private let tabView = TabView() private let controllers: [UIViewController] = [] override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(contentView) view.addSubview(tabView) } }
最初に選択したいViewControllerを追加します。
let controller = controllers[0] controller.willMove(toParent: self) controller.view.frame = view.bounds addChild(controller) contentView.addSubview(controller.view)
タグが切り替わった際に以下のようにメソッドを呼びます
func didSelectTag(_ selectedIndex: Int) { let fromController = selectedViewController let toController = controllers[selectedIndex] selectedViewController = toController self.remove(controller: fromController) self.add(controller: toController) }
add, removeメソッドの実装はこのようにします。
func add(controller: UIViewController) { addChild(controller) controller.view.frame = view.bounds contentView.addSubview(controller.view) controller.didMove(toParent: self) } func remove(controller: UIViewController) { controller.willMove(toParent: nil) controller.removeFromParent() controller.view.removeFromSuperview() }
またUITabBarViewConntrollerとライフサイクルイベントの発火タイミングを揃えるとこんな感じになります。 toViewController.begin, fromViewController.begin, fromViewController.end, toViewController.end とfromではさんであげる必要があります。
func didSelectTab(_ selectedIndex: Int) { guard let fromViewController = selectedViewController, viewControllers.count > selectedIndex else { return } let toViewController = viewControllers[selectedIndex] if fromViewController !== toViewController { toViewController.beginAppearanceTransition(true, animated: true) fromViewController.beginAppearanceTransition(false, animated: true) add(controller: toViewController) remove(controller: fromViewController) fromViewController.endAppearanceTransition() toViewController.endAppearanceTransition() selectedViewController = toViewController } }
上記のようにお作法に従えばUITabViewControllerとほぼ同じなContainerViewControllerを作成することができます 実際に手を動かさないと理解をできないのとメソッドを呼ばなくても動いたりするのでちょっと勉強しにくい コピペで動くものを一応貼っておきます。
Custom TabBarViewController · GitHub
参考
ちゃんとドキュメントがあって助かった。
https://cocoacasts.com/managing-view-controllers-with-container-view-controllers
https://github.com/mixi-inc/iOSTraining/wiki/2.3-Custom-Container-View-Controller