Storyboardで別のViewを埋め込めるCustomViewを作る

#概要
Xibで作ったCustomView内に表示する画面を後からViewControllerで定義し、Segueで接続します。

<img width="1222" alt="スクリーンショット 2019-02-21 22.51.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/bc327021-f5bb-7c77-1069-3568cdfb1c5b.png">

UITabBarControllerやUINavigationBarなどはStoryboard上でそのコントローラーの中に表示する画面をSegueでつなぐことで設定できます。つまり、Controllerで表示を切り替える複数の画面の実装を使用者にゆだねているわけです。
<img width="300" alt="スクリーンショット 2019-02-21 17.33.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/3ff565d4-345a-88dc-a643-6ca3861e9259.png">
今回、独自実装のNavigationBarをXibで作った際に、再利用性を高めるため表示内容を後から変更できるようにする必要がありました。そこでUITabBarControllerのようにCustomViewでもSegueで表示するViewControllerを指定できるようにしました。

この記事ではこの方法を解説しています。

#前提
この記事はxibで再利用可能なView（以下CustomView）を作りStoryboardで呼び出す方法をご存じであることを前提にしています。

CustomViewの作り方自体は、こちらの記事、[xibで作ったCustomViewをStoryboardで使う]( https://qiita.com/ocadaruma/items/60710cb18cd95a9954ec) で解説されています。

<details><summary>CustomViewを継承したViewをStoryboardで用意しその子として配置されたViewに対して処理をする方法ではなぜダメか</summary>CustomViewの直下にViewをひとつ置く場合は問題ありませんが、画面を切り替える場合のように、複数のViewが並んでいる場合区別をつけにくくななったり、重なっている場合だと編集しにくくなったりする問題があるかと思います。</details>
#今回作るもの
この方法を使ったCustomViewは色々と使いようがあると思いますが、わかりやすさのためにUITabBarControllerを真似たものを作ってみます。

#作り方
###Xibとクラスの用意
以下のファイル・クラスを用意してください。

- MyTabBar.xib
- ```class MyTabBar: UIViewController```
- ```class MyTabBarView: UIView```
- ```class MyTabBarRelation: UIStoryboardSegue```

###xibファイルの設定

- File's ownerをMyTabBarに設定
- xibにはUIViewを一つ置く
- ViewのClassはMyTabBarViewに設定
- ViewのSubviewとしてUIViewを一つ、UIButtonを二つ置く

<img width="831" alt="スクリーンショット 2019-02-21 20.48.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/b5297028-dfd0-eba7-4c77-a46f404023c9.png">

画像はわかりやすさのためにUIViewに色をつけています。配置は適当にしてください。

###MyTabBarView (UIView)
####プロパティの用意

MyTabBarからsegueにアクセスするためここでIBOutletを設定しておきます。このクラスを用意せずにMyTabBarで定義することも可能ですが、MyTabBarクラスは再利用するため@IBOutletが表示されるのはよくないのでこのクラスを用意しています。

```Swift
class MyTabBarView: UIView {
    @IBOutlet var contentView: UIView!
}
```

###MyTabBar（UIViewController）
####プロパティの用意
最終的にこのコントロールを利用する際はMyTabBarを、設定したいViewControllerに継承させます。

以下のプロパティを用意します。

- ```@IBInspectable var segueIdentifier1: String = ""```
- ```@IBInspectable var segueIdentifier2: String = ""```
- ```weak var childViewController1: UIViewController?```
- ```weak var childViewController2: UIViewController?```

筆者の調査不足かもしれませんが、segueでつながっているViewControllerの一覧を取得する方法は無かったためAttributes Inspectorでsegue identifierを設定するようにしています。

childViewControllerのプロパティはMyTabBarを継承したクラスで接続先にアクセスするために用意します。
####segueで接続しているViewの内容を読み込む
とりあえず初期でsegueIdentifier1でつながっている画面をviewに表示します。

viewDidLoadでxibを読み込み、segueIdentifier1で指定されたsegueを実行します。segueIdentifierが間違っていたとき、入力されていないときなどの例外処理は追加で必要です。

```Swift
override func viewDidLoad() {
        super.viewDidLoad()
        view = Bundle(for: type(of: self)).loadNibNamed("MyTabBar", owner: self, options: nil)!.first as? UIView
        performSegue(withIdentifier: segueIdentifier1, sender: self)
    }
```

####Buttonを押したときの処理
Button1を押したときにidentifier1の内容、Button2を押したときにidentifier2の内容を表示するようにします。

```Swift
    @IBAction func button1Tapped() {
        performSegue(withIdentifier: segueIdentifier1, sender: self)
    }
    
    @IBAction func button2Tapped() {
        performSegue(withIdentifier: segueIdentifier2, sender: self)
    }
```

File's ownerからUIButtonに@IBActionの接続を行います。

####MyTabBarのコード全体

```Swift
class MyTabBar: UIViewController {

    @IBInspectable var segueIdentifier1: String = ""
    @IBInspectable var segueIdentifier2: String = ""
    weak var childViewController1: UIViewController?
    weak var childViewController2: UIViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view = Bundle(for: type(of: self)).loadNibNamed("MyTabBar", owner: self, options: nil)!.first as? UIView
        performSegue(withIdentifier: segueIdentifier1, sender: self)
    }
    
    @IBAction func button1Tapped() {
        performSegue(withIdentifier: segueIdentifier1, sender: self)
    }
    
    @IBAction func button2Tapped() {
        performSegue(withIdentifier: segueIdentifier2, sender: self)
    }

}
```

###カスタムsegueを作る

segueは通常pushやpopなどを使用しますが、UIStoryboardSegueを継承したカスタムsegueを作ることが可能です。カスタムsegueを使えば画面遷移のアニメーションを独自のものにしたりできます。今回はこれを使ってsegue先を指定したviewに表示するようにします。

performメソッドをoverrideして独自の画面遷移を作ります。UIStoryboaedSegueクラスでは以下の情報が取得できます。

```Swift
var source: UIViewController
var destination: UIViewController
var identifier: String?
```

今回、sourceはMyTabBarなのでcontentViewにdestinationのviewを貼り付けるだけです。

```Swift
class MyTabBarRelation: UIStoryboardSegue {
    override func perform() {
        let targetView = (source.view as! MyTabBarView).contentView
        source.addChild(destination)
        
        for view in targetView!.subviews {
            view.removeFromSuperview()
        }
        
        //MyTabBarのpropertyにdestinationを設定することで、MyTabBarを継承したクラスからアクセスできるようにする
        switch identifier {
        case (source as! MyTabBar).segueIdentifier1:
            (source as! MyTabBar).childViewController1 = destination
            break
        case (source as! MyTabBar).segueIdentifier1:
            (source as! MyTabBar).childViewController2 = destination
            break
        default:
            break
        }
        
        targetView?.addSubview(destination.view)
        destination.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            destination.view.topAnchor.constraint(equalTo: targetView!.topAnchor),
            destination.view.bottomAnchor.constraint(equalTo: targetView!.bottomAnchor),
            destination.view.leadingAnchor.constraint(equalTo: targetView!.leadingAnchor),
            destination.view.trailingAnchor.constraint(equalTo: targetView!.trailingAnchor)
            ])
    }
}
```

### segueをstoryboardで接続
プロジェクトを作ったときに用意されているViewController.swiftとMain.storyboardを使用して実際に表示する画面を作ります。

storyboardに三つのViewControllerを用意し、initial View Controllerは初期に設定されているViewを削除し、ViewController（プロジェクトを作ったときに用意されているクラス）を継承させます。

<img width="993" alt="スクリーンショット 2019-02-21 22.15.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/83c8c979-441f-c01c-5939-75eea41ad09b.png">

タブで切り替える二つのViewControllerを用意し、それぞれわかりやすいように色をつけておきます。

ViewControllerにはMyTabBarを継承させます。

```Swift
class ViewController: MyTabBar {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
```

ここでMyTabBarを継承したViewControllerと切り替えるViewControllerを接続します。

<img width="717" alt="スクリーンショット 2019-02-21 22.20.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/a534e8a6-a616-8674-0c21-eebecb5c3631.png">

ViewControllerから右クリックで切り替える緑色のView上にSegueを追加しようとするとmy tab bar relationが用意されているのがわかると思います。UIStoryboardSegueを継承したクラスがあると自動的に追加されるようです。

my tab bar relationを接続しidentifierを設定します。
<img width="276" alt="スクリーンショット 2019-02-21 22.23.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/63acce04-6031-fad3-3d11-01e4a65895ef.png">

ここで設定したidentifierをVierControllerのAttribute InspectorでSegueIdentifier1にも入力します。この項目はViewControllerがTabBarを継承しているので@IBInspectableを指定したプロパティが表示されているはずです。

<img width="265" alt="スクリーンショット 2019-02-21 22.24.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/327967/2614111b-8799-53e1-a7c1-9e9821ce336d.png">


同じようにもう一つのViewControllerにもSegueをつなげる作業を繰り返します。

#おわりに
custom segueを使用してcustom view上に表示する内容を別のview controllerから読み込むことができます。codeで追加する場合はこれを意識せずにviewをaddSubviewすれば良いだけですが、storyboardを活用することで再利用性が高くなるかと思います。

#注意
わかりやすさのために例外処理などを省いていますのでお気をつけください