[iOS]子ViewControllerを動的に入れ替えるメモ


## 参考

[Switching Child View Controllers in iOS with Auto Layout](https://spin.atomicobject.com/2015/10/13/switching-child-view-controllers-ios-auto-layout/)

英語がわかるなら、上記のURLがすべて。

## 概要

下図の黄色のビューを、動的に入れ替える話。

![スクリーンショット 2018-03-04 14.15.09.png](https://qiita-image-store.s3.amazonaws.com/0/20722/aa13b22f-9caf-a47f-1e42-9b338d985455.png "スクリーンショット 2018-03-04 14.15.09.png")

## コード

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    enum CenterViewType {
        case First
        case Second
        case Third
        
        func id() -> String {
            switch self {
            case .First:
                return "First"
            case .Second:
                return "Second"
            case .Third:
                return "Third"
            }
        }
    }
    
    @IBAction func firstButtonTapped(_ sender: Any) {
        self.changeCenterView(.First)
    }
    
    @IBAction func secondButtonTapped(_ sender: Any) {
        self.changeCenterView(.Second)
    }

    @IBAction func thirdButtonTapped(_ sender: Any) {
        self.changeCenterView(.Third)
    }

    @IBOutlet weak var centerView: UIView!
    
    var currentViewController: UIViewController?

    func addSubview(_ subView:UIView, toView parentView:UIView) {
        parentView.addSubview(subView)
        
        var viewBindingsDict = [String: AnyObject]()
        viewBindingsDict["subView"] = subView
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "V:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
    }
    
    func changeCenterView(_ type: CenterViewType) {
        let identifier = type.id()
        let newViewController = self.storyboard?.instantiateViewController(withIdentifier: identifier)
        newViewController!.view.translatesAutoresizingMaskIntoConstraints = false
        self.cycleFromViewController(self.currentViewController!, toViewController: newViewController!)
        self.currentViewController = newViewController
    }
    
    func cycleFromViewController(_ oldViewController: UIViewController, toViewController newViewController: UIViewController) {
        oldViewController.willMove(toParentViewController: nil)
        self.addChildViewController(newViewController)
        self.addSubview(newViewController.view, toView:self.centerView!)
        // TODO: Set the starting state of your constraints here
        newViewController.view.layoutIfNeeded()
        
        // TODO: Set the ending state of your constraints here
        
        UIView.animate(withDuration: 0.5,
            animations: {
                // only need to call layoutIfNeeded here
                newViewController.view.layoutIfNeeded()
            },
            completion: { finished in
                oldViewController.view.removeFromSuperview()
                oldViewController.removeFromParentViewController()
                newViewController.didMove(toParentViewController: self)
            }
        )
    }
    
    override func viewDidLoad() {
        self.currentViewController = self.storyboard?.instantiateViewController(withIdentifier: CenterViewType.First.id())
        let view = self.currentViewController!.view!
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addChildViewController(self.currentViewController!)
        self.addSubview(self.currentViewController!.view, toView: self.centerView)

        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
```

## コードの解説

"First","Second","Third"と表示されたボタンを押すと

```swift
    @IBAction func firstButtonTapped(_ sender: Any) {
        self.changeCenterView(.First)
    }
    
    @IBAction func secondButtonTapped(_ sender: Any) {
        self.changeCenterView(.Second)
    }

    @IBAction func thirdButtonTapped(_ sender: Any) {
        self.changeCenterView(.Third)
    }
```

それぞれ、上記のアクションが呼び出され、どれを選んでも ```changeCenterView()``` メソッドが呼び出される。

```changeCenterView()``` では、

```swift:
    func changeCenterView(_ type: CenterViewType) {
        let identifier = type.id()
        let newViewController = self.storyboard?.instantiateViewController(withIdentifier: identifier)
        newViewController!.view.translatesAutoresizingMaskIntoConstraints = false
        self.cycleFromViewController(self.currentViewController!, toViewController: newViewController!)
        self.currentViewController = newViewController
    }
```

storyboard id (identifier) から該当する子ViewControllerを生成し、 ```cycleFromViewController()``` メソッドを呼び出す。

```swift:
    func cycleFromViewController(_ oldViewController: UIViewController, toViewController newViewController: UIViewController) {
        oldViewController.willMove(toParentViewController: nil)
        self.addChildViewController(newViewController)
        self.addSubview(newViewController.view, toView:self.centerView!)
        // TODO: Set the starting state of your constraints here
        newViewController.view.layoutIfNeeded()
        
        // TODO: Set the ending state of your constraints here
        
        UIView.animate(withDuration: 0.5,
            animations: {
                // only need to call layoutIfNeeded here
                newViewController.view.layoutIfNeeded()
            },
            completion: { finished in
                oldViewController.view.removeFromSuperview()
                oldViewController.removeFromParentViewController()
                newViewController.didMove(toParentViewController: self)
            }
        )
    }
```

```cycleFromViewController()``` では古いViewControllerを新しいViewControllerと取り替える。
重要なのが、 ```addSubview(toView:)``` メソッド。

```swift:
    func addSubview(_ subView:UIView, toView parentView:UIView) {
        parentView.addSubview(subView)
        
        var viewBindingsDict = [String: AnyObject]()
        viewBindingsDict["subView"] = subView
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
        parentView.addConstraints(
            NSLayoutConstraint.constraints(
                withVisualFormat: "V:|[subView]|",
                options: [],
                metrics: nil,
                views: viewBindingsDict))
    }
```

```addSubview(toView:)``` では、AutoLayoutを適切に設定してやることにより、子ViewControllerが元の黄色いView内にぴったり収まるようにしている（自力で子ViewControllerを入れ替えようとしてハマるのが、この部分）。





## GitHubレポジトリ

出来上がったプロジェクトが以下。

[https://github.com/JunSuzukiJapan/SwitchChildViewControllers](https://github.com/JunSuzukiJapan/SwitchChildViewControllers)