[iOS] 上部のタブでViewControllerを切り替えたいとき

# SegmentedControlとContainer View Controllersによるタブ切り替え

## 概要

- `SegmentedControl` の切り替えで、複数の子ViewControllerを切り替える

## 完成イメージ

<img width="320" alt="Simulator Screen Shot - iPhone SE - 2018-07-05 at 04.56.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/4852d24e-f248-e151-2997-2ca8f156d4d3.png">

<img width="320" alt="Simulator Screen Shot - iPhone SE - 2018-07-05 at 04.56.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/9160a125-0398-1c8f-18d8-6f084d491bc2.png">

## 特徴

- 子ViewControllerは、親ViewControllerと同じサイズにフィットさせる
- 子ViewControllerは、最初に呼ばれるまでインスタンス化されない
- 一度インスタンス化されるとプロパティにセットされているため、再度切り替え時でもすぐに使える
- 親ViewControllerは、SegmentedControlによる切り替えだけを担当し、子ViewControllerとの責務の分離ができる
- UITabBarController と同じような機能を自作する感じで、異なる点はViewControllerを配列形式で保持しない点

## 手順

- `SegmentedControl` をタップすることで、 `Sample1ViewController` と `Sample2ViewController` を切り替える場合の例


### ViewControllerファイルの準備

- 親ViewControllerと2つの子ViewControllerを用意
    - 子ViewControllerは、 `Sample1ViewController` と `Sample2ViewController` の名前で作成

<img width="179" alt="スクリーンショット 2018-07-05 5.05.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/85d050a7-ac71-e50e-0af6-f09166c4993f.png">


### Storyboard側の準備

- ViewControllerを3つ配置
- 1つを親ViewController用にする
    - ViewController部分を選択し `Editor` > `Embed In` > `Navigation Controller` から、ナビゲーションコントローラーを追加
    - ナビゲーションコントローラーに、 `Utility` パネル下部の `Show the Object Library` から `Segmented Control` をドラッグ＆ドロップで追加
- 2つを子ViewController用にする
    - 1つを `Custom Class` を `Sample1ViewController` にし、 `Storyboard ID` も `Sample1ViewController` にする
        - 切り替えが分かりやすいように、Viewの背景色を緑色に指定
    - もう1つを `Custom Class` を `Sample2ViewController` にし、 `Storyboard ID` も `Sample2ViewController` にする
        - 切り替えが分かりやすいように、Viewの背景色を黄色に指定

<img width="565" alt="スクリーンショット 2018-07-05 5.06.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/f2c993d0-f32f-dc0d-91a4-7c3010e01756.png">


### Storyboardの親ViewControllerに設置したSegmentedControlをIBOutletで紐づけ

```swift
@IBOutlet weak var segmentedControl: UISegmentedControl!
```

<img width="596" alt="スクリーンショット 2018-07-05 4.36.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/9b3af70c-e824-62f6-895e-c51d6dc35feb.png">



### 子ViewControllerの追加/削除メソッド

```swift
private func add(asChildViewController viewController: UIViewController) {
    // 子ViewControllerを追加
    addChildViewController(viewController)
    // Subviewとして子ViewControllerのViewを追加
    view.addSubview(viewController.view)
    // 子Viewの設定
    viewController.view.frame = view.bounds
    viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    // 子View Controllerへの通知
    viewController.didMove(toParentViewController: self)
}

private func remove(asChildViewController viewController: UIViewController) {
    // 子View Controllerへの通知
    viewController.willMove(toParentViewController: nil)
    // 子ViewをSuperviewから削除
    viewController.view.removeFromSuperview()
    // 子View Controllerへの通知
    viewController.removeFromParentViewController()
}
```

### 親ViewControllerで、子ViewControllerの指定

```swift
private lazy var sample1ViewController: Sample1ViewController = {
    let storyborad = UIStoryboard(name: "Main", bundle: Bundle.main)
    var viewController = storyborad.instantiateViewController(withIdentifier: "Sample1ViewController") as! Sample1ViewController
    add(asChildViewController: viewController)
    return viewController
}()

private lazy var sample2ViewController: Sample2ViewController = {
    let storyborad = UIStoryboard(name: "Main", bundle: Bundle.main)
    var viewController = storyborad.instantiateViewController(withIdentifier: "Sample2ViewController") as! Sample2ViewController
    add(asChildViewController: viewController)
    return viewController
}()
```

### SegmentedControl切り替え時のView更新

- Storyboardの親ViewControllerに設置した `Segmented Control` から `IBAction` で紐づけ
    - `Connection` を `Action` にする
    - メソッド名を `tapSegmentedControl` にし、 `Type` を `UISegmentedControl` にする
    - `Type` を `UISegmentedControl` にする

<img width="312" alt="スクリーンショット 2018-07-05 4.45.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/29536/a606f112-ee61-e6d6-8330-f26578846b71.png">


```swift
private func updateView() {
    if segmentedControl.selectedSegmentIndex == 0 {
        remove(asChildViewController: sample2ViewController)
        add(asChildViewController: sample1ViewController)
    } else {
        remove(asChildViewController: sample1ViewController)
        add(asChildViewController: sample2ViewController)
    }
}

@IBAction func tapSegmentedControl(_ sender: UISegmentedControl) {
    updateView()
}
```

### viewDidLoad時の指定

```swift
override func viewDidLoad() {
    super.viewDidLoad()
    setupView()
}

private func setupView() {
    updateView()
}
```

## 子ViewControllerが呼ばれた際の確認

- Sample1ViewController.swift

```swift
override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated)
   print("Sample1ViewController Will Appear")
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    print("Sample1ViewController Will Disappear")
}
```

- Sample2ViewController.swift

```swift
override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated)
   print("Sample2ViewController Will Appear")
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    print("Sample2ViewController Will Disappear")
}
```

## 親ViewControllerの完成コード

```swift
import UIKit

class ViewController: UIViewController {
    
    // MARK: - Property
    
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    
    private lazy var sample1ViewController: Sample1ViewController = {
        let storyborad = UIStoryboard(name: "Main", bundle: Bundle.main)
        var viewController = storyborad.instantiateViewController(withIdentifier: "Sample1ViewController") as! Sample1ViewController
        add(asChildViewController: viewController)
        return viewController
    }()
    
    private lazy var sample2ViewController: Sample2ViewController = {
        let storyborad = UIStoryboard(name: "Main", bundle: Bundle.main)
        var viewController = storyborad.instantiateViewController(withIdentifier: "Sample2ViewController") as! Sample2ViewController
        add(asChildViewController: viewController)
        return viewController
    }()
    
    // MARK: - View Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }
    
    // MARK: - View Methods
    
    private func setupView() {
        updateView()
    }
    
    private func updateView() {
        if segmentedControl.selectedSegmentIndex == 0 {
            remove(asChildViewController: sample2ViewController)
            add(asChildViewController: sample1ViewController)
        } else {
            remove(asChildViewController: sample1ViewController)
            add(asChildViewController: sample2ViewController)
        }
    }
    
    // MARK: - Action
    
    @IBAction func tapSegmentedControl(_ sender: UISegmentedControl) {
        updateView()
    }
    
    // MARK: - Child View Controller Operation Methods
    
    private func add(asChildViewController viewController: UIViewController) {
        // 子ViewControllerを追加
        addChildViewController(viewController)
        // Subviewとして子ViewControllerのViewを追加
        view.addSubview(viewController.view)
        // 子Viewの設定
        viewController.view.frame = view.bounds
        viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        // 子View Controllerへの通知
        viewController.didMove(toParentViewController: self)
    }
    
    private func remove(asChildViewController viewController: UIViewController) {
        // 子View Controllerへの通知
        viewController.willMove(toParentViewController: nil)
        // 子ViewをSuperviewから削除
        viewController.view.removeFromSuperview()
        // 子View Controllerへの通知
        viewController.removeFromParentViewController()
    }
    
}
```


## 環境

- Xcode 9.4.1

## 参照

- [Managing View Controllers With Container View Controllers](https://cocoacasts.com/managing-view-controllers-with-container-view-controllers)