新規にiOSアプリ開発を始めた学生エンジニアに取ってUITabBarのカスタマイズは地獄だった話

>> iPhone XR, XS, XS Maxにも対応しました。

僕はもともとWeb Applicationの開発をしてた人なので、スマホアプリのUIもWebViewを使って書いていました。(正確に言うとIonicとかOnsenUIとか) Web UIでも工夫次第ではネイティブUIにひけをとらないのですが、アニメーションやデバイスセンサを扱う部分など、やはりネイティブにはかないません。
そこで、iOSのネイティブUI(UIKit)でフロントを作ろうとしたのですが「Reactヤベェ！Angularヤベェ」とWebの世界でいきっていた人にとっては辛い例があったので備忘録代わりにまとめました。

## `UITabBar`のアイコンのタイトルを消したい
iOS標準のTabBarはこんな感じですよね。アイコンの下にタイトルがあるオーソドックスなUIです。

<img width="374" alt="Screen Shot 2017-10-21 at 21.55.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/44018/35d70c28-ecfa-c393-fb13-aaa4ad939051.png">

しかし、InstagramのTabBarのようにアイコンのみのシンプルなものが欲しい場合もあるでしょう。

<img width="375" alt="IMG_3203.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/44018/6ce0be03-3226-3ee9-82fe-cad1d4d29a83.jpeg">

最初は`disableLabel = true`のようなプロパティを`false`にすることで消えるかなっと楽観的に考えていたのですが、どうやらない！
https://developer.apple.com/documentation/uikit/uitabbaritem を見ると`title`というgetter setterプロパティがありました。
ということで、`title`をoverrideしちゃいましょう。するとラベルが消えますがアイコンの位置がずれてしまうのでimageInsetsもoverrideして位置を調整します。

```swift:CustomTabBarItem.swift
import UIKit

class CustomTabBarItem: UITabBarItem {
    
    override var title: String? {
        get {
            return nil
        }
        
        set {
            super.title = title
        }
    }
    
    
    override var imageInsets: UIEdgeInsets {
        get {
            return UIEdgeInsets(top: 5, left: 0, bottom: -5, right: 0)
        }
        
        set {
            super.imageInsets = imageInsets            
        }
    }

}
```

### iOS11ではUIEdgeInsetsを調整してもアイコンが天地中央にならない!
こんな感じです。要はtop, bottom, right, left全て0にすると言うことです。
`#available`を使ってiOS11のみoffsetを0にすることでiOS11以下のバージョンもサポートすることができます。

```swift:CustomTabBar.swift
class CustomTabBar: UITabBar {

override func layoutSubviews() {
    super.layoutSubviews()

    var offset: CGFloat = 6.0

    if #available(iOS 11.0, *), traitCollection.horizontalSizeClass == .regular {
        offset = 0.0
    }

    let imageInset = UIEdgeInsets(
        top: offset,
        left: 0.0,
        bottom: -offset,
        right: 0.0
    )

    for tabBarItem in items ?? [] {
        tabBarItem.title = ""
        tabBarItem.imageInsets = imageInset
    }
}

```



## `UITabBar`の高さを変更したい

TabBarの高さをカスタマイズするにはUITabBarの継承クラスを作り`func sizeThatFits`をoverrideします。
この時に注意したいのがiPhone XやXR, XSという~~変な画面の余計な端末~~素晴らしく画期的な端末向けのレイアウトです。今までホームボタンがあった場所が画面になっているのでその分の高さを取得してTabBarの高さに加えてやらないと、TabBarがiPhone Xの丸っこいエッジに落ちて悲惨なことになります。

**追記**
`self.safeAreaInsets.bottom`でセーフエリアの下の高さを取得することができますが、この値は`viewWillLayoutSubviews `が実行された後に更新されます。つまり、`viewWillLayoutSubviews`これが実行される前は0が返ってくるので`UIApplication.shared.windows[0].safeAreaInsets.bottom`を用います。


```swift
import UIKit

class CustomUITabBar: UITabBar {
    
    // ここでTabBarの高さを設定
    let tabBarHeight: CGFloat = 70

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        super.sizeThatFits(size)

        let safeAreaBottom = UIApplication.shared.windows[0].safeAreaInsets.bottom
        
        var size = super.sizeThatFits(size)

        if #available(iOS 11.0, *) {
            size.height = safeAreaBottom + tabBarHeight
        } else {
            size.height = tabBarHeight
        }

        return size
    }
}
```


## アイコンのカラーを変えたいけどRGB表記だるいわ
iOS界隈では割と一般的な工夫かもしれませんが、UIColorを指定したい場合は`UIColor(red:0.20, green:0.20, blue:0.20, alpha:1.0)`のようにRGB + Alphaで指定しなければなりません。僕みたいにWeb作ってたマンからすると面倒で仕方ない。CSS(まぁSass使いますけど)みたいに`#333333`と16進で指定したいところです。
実装としては`UIColor`を拡張してinitializerに`hex`と`alpha`の引数を持たせて内部で通常のRGBに直します。ポイントはhexを`String`としてとることです。数値型だと`0x`を先頭につけなければなりません。CSSいじってたマンにとっては`0xFFFFFF`なんてちょっとキモイですね。

```swift
import UIKit

extension UIColor {
    convenience init(hex: String, alpha: CGFloat = 1.0) {
        if hex.count == 6 {
            let red: CGFloat = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
            let green: CGFloat = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
            let blue: CGFloat = CGFloat(rgb & 0x0000FF) / 255.0
        }
        
        self.init(red: red, green: green, blue: blue, alpha: alpha)
    }
}
```