最近タブバーの上の情報を載るバーを追加したかったので、UITabBarをカスタマイズしてみました。
実現したい内容:
- アプリの使用中に、条件によって、バーが表示/非表示に変更できる
- タブの切り替えはバーの表示に影響を与えない
実装方針
実装イメージ

バーを表示したい時
- UITabBarの高さをバーの高さ分上げる
- barViewを子ビューとしてUITabBarの上に追加する
バーを非表示したい時
- barViewをUITabBarから外す
- UITabBarの高さを元に戻す
スペック
- iPhone 11(iOS 14.0)
- Xcode 12.0
準備
タブが2つあるアプリを作成し、画面レイアウトの変更をわかりやすくするため、それぞれ黄色と赤の子ビューを追加します。


実装コード
barViewの高さを36.0に設定します。
public static let BarViewHeight: CGFloat = 36.0
UITabBarの高さを変更するため、以下のコードを実行します。
public func showBar() {
// タブバーの高さを変更する
object_setClass(tabBar, CustomTabBar.self)
self.addBarView()
isBarViewShowing = true
}
class CustomTabBar: UITabBar {
override func sizeThatFits(_ size: CGSize) -> CGSize {
super.sizeThatFits(size)
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = sizeThatFits.height + CustomTabBarController.BarViewHeight
return sizeThatFits
}
}
目立つようにbarViewの背景色を青に設定し、実装後に画面の「表示」ボタンを押すと以下のようになりました。

タブの画像アイコンがbarViewに食い込んでいます。
そのため以下のようにUITabBarの画像位置を調整します。
// 画像を下に18ずらす
for item in (self.tabBar.items)! {
item.imageInsets = UIEdgeInsets(top: CustomTabBarController.BarViewHeight/2,
left: 0,
bottom: -CustomTabBarController.BarViewHeight/2,
right: 0)
}
これでbarViewは実装イメージ通りに実装できました。

今度はbarViewが非表示の時に元に戻したいので、barViewが非表示の時に下記メソッドを実行します。
public func hideBar() {
barView.removeFromSuperview()
// タブバーの高さを変更する
object_setClass(tabBar, UITabBar.self)
// 画像を元の位置に戻す
for item in (self.tabBar.items)! {
item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
isBarViewShowing = false
}
以上のコードを実装し、barViewが表示している状態でタブ切り替えしてからbarViewが非表示になると、以下のような画面崩れが発生してしまいました。

原因はUITabBarの高さが変更されましたが、画面のフレームが更新されていないことでした。そのためbarViewを表示・非表示した後に以下のメソッドを呼び、フレームを強制的に更新する必要があります。
private func currentViewLayoutSubviews() {
self.viewControllers?[selectedIndex].view.setNeedsLayout()
self.viewControllers?[selectedIndex].view.layoutSubviews()
}
おわりに
以上でUITabBarのカスタマイズができました。
デザイン変更だけのカスタマイズの経験が多いのですが、UITabBarをリアルタイムに変更するのは初めてなので、実行する前にフレーム更新などは全然考えていませんでした。
UITabBarをカスタマイズする方もいると思うので参考になれば幸いです。