Container利用時のViewControllerの生成順と、nibからViewを作るメモ

#概要
Swift3で、カスタムContainerViewControllerにViewControllerを子としてセットし、その子ViewControllerが、xibファイルから生成するViewを持ち、そのViewも、別のxibから生成されるviewをメンバーに持つ、といった階層構造を作ろうとしたら、各コントロールがいつ生成されるのかわからなくて躓いたので、メモする。

#前提
以下のような階層構造を想定する。
ContainerViewController  　　　// Main.storyboardでデザイン
　　┣ SubViewController　　　　// Main.storyboardでデザイン
　　┃　　　┗ CustomButton 　　　// CustomButton.xibでデザイン
　　┃　　　　　　┗ ExtendLabel  // ExtendLabel.xibでデザイン
　　┃　　　　　　　　　┗ UILabel
　　┗ SubViewController
　　　　　　┗ 以下同様

#結論
Swiftでは以下のような順序で各要素が初期化される。

- ContainerViewController.init
    - ここでSubViewControllerのインスタンス生成をマニュアルで実施。
    - ただしこの段階では、IB上に配置されたUIはロードされてない
- ContainerViewController.viewDidLoad
- ContainerViewController.viewWillAppear
    - ここでContainerViewControllerのメソッドでSubViewControllerをContainerに追加する定番処理を実施
    - addChildViewController(nextVC)
    - nextVC.view.frame = view.frame　
        - // (コメントから)viewがロードされていないので、viewにアクセスのあるこのタイミングでViewとその要素の初期化が走る。
        - CustomButton.init()
        - ExtendLabel.init()
        - ExtendLabel.awakeFromNib()
        - CustomButton.awakeFromNib()
        - SubViewController.viewDidLoad()
    - view.addSubview(nextVC.view)
    - nextVC.didMove(toParentViewController: self)
        - // didMove()の呼び出し完了後じゃないと、SubViewControllerのメンバがnilのままなことがあるので、メンバにアクセスするならこれ以降
- SubViewController.willViewAppear()
- ContainerViewController.viewibDidAppear()
- 表示完了

## xibからカスタムUIViewを生成するためのステップ
- File's Ownerをちゃんと指定する。
    - xiibを開いての左上のFile's Ownerを選択して、右上に表示されるclass指定欄にカスタムクラス名を指定
    - rootViewのclass名に自分のクラス名を入れるのはNG
- ルートのviewをいつもの手順でBindする。
-  self.addSubview(rootView)で自分にViewをBind
- 要するに以下のような感じ

```swift:Viewクラスの例
class ExtendedLabel: UIView {
    @IBOutlet var view: UIView!        // IBからBINDする
    @IBOutlet weak var label: UILabel! // IBからBINDする
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        Bundle.main.loadNibNamed("ExtendedLabel", owner: self, options: nil)
        self.addSubview(self.view);  // こんな感じで自分自身のRootViewをaddSubViewする
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
```

なお、公式ドキュメントには、以下のような記述もある。

- 子 View Controller のルートビューにのみアクセスする。コンテナは、各子のルートビューにのみアクセスする必要があります。つまり、子の view プロパティにより返されるビューです。子の他のビューにはアクセスしないようにしてください。
- 子 View Controller が持つコンテナの情報は最低限にする必要がある。子 View Controller は、自身のコンテンツに焦点を当てる必要があります。子の影響を受ける動作がコンテナで許容される場合、デリゲーション設計パターンを使用してそれらの操作を管理する必要があります。
- まず標準ビューを使用してコンテナを設計する。標準ビュー (子 View Controller のビューではなく) を使用すると、簡略化された環境でレイアウト制約とアニメーション化された遷移をテストするチャンスが得られます。標準ビューが正常に機能した場合、そのビューを子 View Controller のビューと入れ替えます。



以上、参考までに。


----
以下大体のサンプルコードを記載する。

```swift:サンプルコード
class ContainerViewController: UIViewController {
    fileprivate var vc1:SubViewController!
    fileprivate var vc2:SubViewController!
    fileprivate var viewing:Bool = 0;
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let sb = UIStoryboard.init(name: "Main", bundle: nil)
        vc1 = sb.instantiateViewController(withIdentifier: "SubViewController") as! SubViewController
        vc2 = sb.instantiateViewController(withIdentifier: "SubViewController") as! SubViewController
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        changeView()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func changeView() {
        viewing = !viewing
        displayViewController()
    }

    fileprivate func displayViewController() {
        if let displaying = childViewControllers.first {
            hideViewController(vc: displaying)
        }
        if (viewing ) {
            nextVC = vc1
        } else {
            nextVC = vc2
        }
        addChildViewController(nextVC)
        nextVC.view.frame = view.frame
        view.addSubview(nextVC.view)
        nextVC.didMove(toParentViewController: self)
        nextVC.setLabel(text: "VC:" + viewing.description)
    }

    fileprivate func hideViewController(vc: UIViewController) {
        vc.willMove(toParentViewController: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParentViewController()
    }
}

class SubViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: CustomButton!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
   
    @IBAction func buttonTapped(_ sender: Any) {
        if let p = self.parent as? ContainerViewController {
            p.changeView()
        }
    }
    func setLabel(text:String) {
        label.text! = text
    }
}

class CustomButton: UIView {
    @IBOutlet weak var view: UIView!
    @IBOutlet weak var label: ExtendLabel!
 
    override func awakeFromNib() {
        super.awakeFromNib();
    }
        
    required init?(coder:NSCoder) {
        super.init(coder: coder)
        Bundle.main.loadNibNamed("CustomButton", owner: self, options: nil)
        self.addSubview(self.view);
    }
}
class ExtendedLabel: UIView {
    @IBOutlet var view: UIView!
    @IBOutlet weak var label: UILabel!
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        Bundle.main.loadNibNamed("ExtendedLabel", owner: self, options: nil)
        self.addSubview(self.view);
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
```