[Swift] カスタムコンポーネントを使用した TabBar 

# はじめに

iPhone には様々なアプリケーションがあり、時に変わったデザインをしたものを見ることがあります。
その中でも `TabBar` のカスタムデザインはたくさんあり、アプリケーションを利用する上で目にする機会が多い `UI` だと思います。

そこで、今回は中央に `UIButton` がある `TabBar` を作成していきたいと思います。

# 環境

- Xcode 12.4
- Swift 5.3.2

# 完成図

|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091516/8aa517d4-2746-e7d5-80d3-c49ae08aac62.png" width="320px" alt="Completed image">|
|:-:|

# 解説

この手順で解説していきたいと思います。

- `UIView` を継承した `CustomBar` クラスを作る。
  - `UIBezierPath` を用いて `CustomBar` となる図形を描画する。
- `UIView` を継承した　`CustomTabBar` クラスを作る
  - `CustomTabBar` クラスに `UIButton` を追加する。 

### UIView を継承したクラス CustomBar を作る。

`draw(_rect:)` は `View` を描画する時、`View` の幅と高さを指定することができるメソッドです。
ここに `UIBezierPath` を使い図形を描画していきます。 (完成図推奨)

```CustomBar.swift

final class CustomBar: UIView {

    let barHeight:CGFloat = 48

    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        backgroundColor = .clear
    }
    
    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        let screenWidth:CGFloat = UIScreen.main.bounds.width
        let screenHeight:CGFloat = UIScreen.main.bounds.height
        let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0

        let path = UIBezierPath()
        
        // 円弧の中央となる値
        let center = CGPoint(x: screenWidth / 2, y: screenHeight - (barHeight + bottomInset))
        // 円の半径
        let radius: CGFloat = 35
        // 円弧の始まりの位置
        let startAngle: CGFloat = CGFloat(Double.pi) * 2 * 180 / 360
        // 円弧の終わりの位置
        let endAngle: CGFloat = CGFloat(Double.pi) * 2 * 360 / 360

        // 円弧を描く (clockwise: は true = 時計回り, false = 反時計周り)
        path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        //　円弧の終わりから右上の角に向かって線を引く
        path.addLine(to: CGPoint(x: screenWidth, y: screenHeight - (barHeight + bottomInset)))
        //  右上の角から右下の角に線を引く
        path.addLine(to: CGPoint(x: screenWidth, y: screenHeight))
        //  右下の角から左下の角に線を引く
        path.addLine(to: CGPoint(x: 0, y: screenHeight))
        // 左下の角から左上の角に線を引く
        path.addLine(to: CGPoint(x: 0, y: screenHeight - (barHeight + bottomInset)))

        // 描画を終了する
        path.close

        // 図の塗りつぶし (注意：内側を塗りつぶすわけではない。)
        // 上記の手順で図を描画すれば問題ない
        // 手順によっては外側が塗りつぶされる
        UIColor().white.setFill()
        path.fill()
    }

}
```

### UIView を継承した CustomTabBar クラスを作る

先に作成した `CustomBar`　を追加します。

``` CustomTabBar.swift

class CustomTabBar: UIView {

     let customBar: Bar = {
        let bar = Bar()
        bar.layer.shadowColor = UIColor.black.cgColor
        bar.layer.shadowOffset = CGSize(width: 0, height: 8)
        bar.layer.shadowRadius = 8
        bar.layer.shadowOpacity = 0.26
        return bar
    }()
    
    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        backgroundColor = .clear
        
        // draw(rect:) で描画サイズは指定しているので view 自体のサイズは全画面を指定します。
        customBar.frame = UIScreen.main.bounds
        addSubview(customBar)
    }
    
    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

```

### RootViewController を作成

無事に図形の描画が行えているはずなので、確認して見たいと思います。

```RootViewController.swift

class RootViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // view の背景色
        self.view.background = UIColor(red: 86/255, green: 36/255, blue:228/255)
        
        // CustomTabBar の生成
        let customTabBar = CustomTabBar(frame: self.view.bounds)
        self.view.addSubview(customTabBar)
    }
}

```

期待通り `TabBar` が表示されました👏
後は中央に `UIButton` を追加すれば完成ですね。

|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091516/ab3373bf-fb34-fd5f-13de-5b422934c435.png" width="320px">|
|:-:|

### UIButton を追加

`CustomTabBar` クラスに `UIButton` を追加します。

``` CustomTabBar.swift

class CustomTabBar: UIView {
    
    // 中央のボタン
    let middleButton: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = .rgb(red: 86, green: 36, blue: 228)
        button.tintColor = .white
        // ボタンで利用したい機能を示す画像を設定
        button.setImage(#imageLiteral(resourceName: "add"), for: .normal)
        button.layer.cornerRadius = 56 / 2
        button.layer.shadowColor = UIColor.black.cgColor
        button.layer.shadowOffset = CGSize(width: 0, height: 6)
        button.layer.shadowRadius = 6
        button.layer.shadowOpacity = 0.26
        return button
    }()

    let customBar: Bar = {
        let bar = Bar()
        bar.layer.shadowColor = UIColor.black.cgColor
        bar.layer.shadowOffset = CGSize(width: 0, height: 8)
        bar.layer.shadowRadius = 8
        bar.layer.shadowOpacity = 0.26
        return bar
    }()
    
    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        backgroundColor = .clear
        
        // draw(rect:) で描画サイズは指定しているので view 自体のサイズは全画面を指定します。
        customBar.frame = UIScreen.main.bounds
        addSubview(customBar)

        let width:CGFloat = UIScreen.main.bounds.width
        let height:CGFloat = UIScreen.main.bounds.height
        let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
        
        let middleButtonWidthHeight: CGFloat = 56
        let middleButtonRadius: CGFloat = middleButtonWidthHeight / 2
        
        let x: CGFloat = (width / 2) - middleButtonRadius
        let y: CGFloat = height - (customBar.barHeight + bottomInset + middleButtonRadius)
        middleButton.frame = CGRect(x: x, y: y, width: middleButtonWidthHeight, height: middleButtonWidthHeight)
        addSubview(middleButton)
    }
    
    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

```

`UIButton` の追加も出来たので最後にアプリを起動してみて問題がないか確認してみましょう。
問題がなければ、完成図のようになっていると思います。

# ソースコード (全文)

```CustomBar.swift

final class CustomBar: UIView {

    let barHeight:CGFloat = 48

    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        backgroundColor = .clear
    }
    
    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        let screenWidth:CGFloat = UIScreen.main.bounds.width
        let screenHeight:CGFloat = UIScreen.main.bounds.height
        let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0

        let path = UIBezierPath()
        
        // 円弧の中央となる値
        let center = CGPoint(x: screenWidth / 2, y: screenHeight - (barHeight + bottomInset))
        // 円の半径
        let radius: CGFloat = 35
        // 円弧の始まりの位置
        let startAngle: CGFloat = CGFloat(Double.pi) * 2 * 180 / 360
        // 円弧の終わりの位置
        let endAngle: CGFloat = CGFloat(Double.pi) * 2 * 360 / 360

        // 円弧を描く (clockwise: は true = 時計回り, false = 反時計周り)
        path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        //　円弧の終わりから右上の角に向かって線を引く
        path.addLine(to: CGPoint(x: screenWidth, y: screenHeight - (barHeight + bottomInset)))
        //  右上の角から右下の角に線を引く
        path.addLine(to: CGPoint(x: screenWidth, y: screenHeight))
        //  右下の角から左下の角に線を引く
        path.addLine(to: CGPoint(x: 0, y: screenHeight))
        // 右下の角から左上の角に線を引く
        path.addLine(to: CGPoint(x: 0, y: screenHeight - (barHeight + bottomInset)))

        // 描画を終了する
        path.close

        // 図の塗りつぶし (注意：内側を塗りつぶすわけではない。)
        // 上記の手順で図を描画すれば問題ない
        // 手順によっては外側が塗りつぶされる
        UIColor().white.setFill()
        path.fill()
    }

}

```

``` CustomTabBar.swift

class CustomTabBar: UIView {
    
    // 中央のボタン
    let middleButton: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = .rgb(red: 86, green: 36, blue: 228)
        button.tintColor = .white
        // ボタンで利用したい機能を示す画像を設定
        button.setImage(#imageLiteral(resourceName: "add"), for: .normal)
        button.layer.cornerRadius = 56 / 2
        button.layer.shadowColor = UIColor.black.cgColor
        button.layer.shadowOffset = CGSize(width: 0, height: 6)
        button.layer.shadowRadius = 6
        button.layer.shadowOpacity = 0.26
        return button
    }()

    let customBar: Bar = {
        let bar = Bar()
        bar.layer.shadowColor = UIColor.black.cgColor
        bar.layer.shadowOffset = CGSize(width: 0, height: 8)
        bar.layer.shadowRadius = 8
        bar.layer.shadowOpacity = 0.26
        return bar
    }()
    
    // イニシャライザ
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        backgroundColor = .clear
        
        // draw(rect:) で描画サイズは指定しているので view 自体のサイズは全画面を指定します。
        customBar.frame = UIScreen.main.bounds
        addSubview(customBar)

        let width:CGFloat = UIScreen.main.bounds.width
        let height:CGFloat = UIScreen.main.bounds.height
        let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
        
        let middleButtonWidthHeight: CGFloat = 56
        let middleButtonRadius: CGFloat = middleButtonWidthHeight / 2
        
        let x: CGFloat = (width / 2) - middleButtonRadius
        let y: CGFloat = height - (customBar.barHeight + bottomInset + middleButtonRadius)
        middleButton.frame = CGRect(x: x, y: y, width: middleButtonWidthHeight, height: middleButtonWidthHeight)
        addSubview(middleButton)
    }
    
    // 必須イニシャライザ
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

```

``` RootViewController.swift

class RootViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // view の背景色
        self.view.background = UIColor(red: 86/255, green: 36/255, blue:228/255)
        
        // CustomTabBar の生成
        let customTabBar = CustomTabBar(frame: self.view.bounds)
        self.view.addSubview(customTabBar)
    }
}

```

# さいごに

今回は特殊なデザインの `TabBar` をカスタムコンポーネントで作成してみました。
どうだったでしょうか？
既存の `UI` も良いですが、このように少し変わった `UI` に興味を持っていくのも良いと思います。

#### おまけ

`CustomTabBar` クラスに追加した `UIButton` の処理は `Protocol・Delegate` を使うと良いと思います。
既存の `UITabBar` のように左右に `Button` を付けたい場合は `CustomTabBar` クラスに追加すると良いと思います。 

# 参考文献

[draw(_:) | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uiview/1622529-draw)
[UIBezierPath | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uibezierpath)