子ビューコントローラーのビューを自分のビューの子ビューにする話

まあ MVC についてもいくつか記事を書いてきたのですが、今までは正直 ViewController と View の分け方についてあんまり深く突っ込んだことがありませんでした。理由は単純：正直自分でも具体的にどう分けるべきか曖昧でした。

いや、理屈は非常に単純で、要するに「見た目をどう表現するか」はビューの仕事で、「ユーザの操作をどう扱うか」はコントローラーの仕事だということは明確です。

ところが、一つ問題があります、[以前の記事](http://qiita.com/lovee/items/555a5b76097347aa2367)にこんなグラフを載せたことがあります：
![MVC 参照](https://qiita-image-store.s3.amazonaws.com/0/63998/277c8e97-4c3d-3df3-7526-6a72d564f592.png)
この図の右半分を見ればわかると思いますが、**サブビューが多すぎ**問題です。

ここまでサブビューが増えてくると、全てを一つのコントローラーで制御するのはちょっと無理があります。いやできないことはないが、すぐ肥大化してしまうことが容易に想像できるでしょう。というわけでコントローラーに子コントローラーを積んで、コントローラーのビューに子コントローラーのビューを子ビューとして表現する必要があったりします。

でも問題は理屈が分かっていても、実際どうやって実現するのか今まではわかりませんでした。なんか `addChildViewController` とか使えばできるんじゃないかと思ってたのですが、なんか子コントローラーがコントロラーの仕事してくれなかったりと意外と難航してました。

ところが最近複雑な実装が増えてきてさすがに一つだけのコントローラーが限界になってきたので、どうしてもこれを実現しないとと思って色々調べてみました。というわけで、まず基本設計としてはこんな感じになるんじゃないかと思います：
![ContainerViewControllerSample.png](https://qiita-image-store.s3.amazonaws.com/0/63998/6fa7781d-e674-a869-5bee-de1e640ab67e.png)

ここで、コントローラーはコントローラーの仕事をして、レイアウトとかの見た目の制御は全てビューで行うのが目的です。では、いざやってみましょう。

まずは最後の子ビューを作ります：

```swift:Subview
class Subview: UIView {
	
	override func didMoveToSuperview() {
		super.didMoveToSuperview()
		self.backgroundColor = .redColor()
	}
	
}
```

次にこの子ビューの見た目を制御する親ビューを：

```swift:View
class View: UIView {
	
	weak var subview: Subview? {
		didSet {
			oldValue?.removeFromSuperview()
			
			if let view = self.subview {
				self.addSubview(view)
			}
		}
	}
	
	override func didMoveToSuperview() {
		super.didMoveToSuperview()
		self.backgroundColor = .whiteColor()
	}
	
	override func layoutSubviews() {
		super.layoutSubviews()
		
		if let view = self.subview {
			let length = min(self.frame.width, self.frame.height) / 2
			view.frame.size.width = length
			view.frame.size.height = length
			view.frame.origin.x = (self.frame.width - view.frame.width) / 2
			view.frame.origin.y = (self.frame.height - view.frame.height) / 2
			view.layer.cornerRadius = length / 2
		}
	}
	
}
```

これで見た目の制御ができました。次にコントローラーを作ります。まずは同じく子から：

```swift:ChildViewController
class ChildViewController: UIViewController {
	
	private lazy var subview: Subview = {
		let view = Subview(frame: self.view.bounds)
		return view
	}()
	
	override func didMoveToParentViewController(parent: UIViewController?) {
		super.didMoveToParentViewController(parent)
		self.loadView()
	}
	
	override func loadView() {
		super.loadView()
		self.view = self.subview
		self.viewDidLoad()
	}
	
	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
		// Dispose of any resources that can be recreated.
	}
	
	override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
		print("Touches ended on child view controller")
	}
	
}
```

ここで大事なのは自分で `loadView()` と `didMoveToParentViewController(_:)` をオーバーライドして、`didMoveToParentViewController(_:)` から自分で `loadView()` を呼び出すことです。そして `loadView()` で自分のビューを `subview` に入れ替えます。ここまで出来たら最後は親コントローラーです：

```swift:ContainerViewController
class ContainerViewController: UIViewController {
	
	lazy var childViewController: ChildViewController = {
		let controller = ChildViewController()
		return controller
	}()
	
	private lazy var baseView: View = {
		let view = View(frame: self.view.bounds)
		return view
	}()
	
	override func loadView() {
		super.loadView()
		self.view = self.baseView
	}
	
	override func viewDidLoad() {
		super.viewDidLoad()
		self.addChildViewController(self.childViewController)
		self.baseView.subview = self.childViewController.view as? Subview
		self.childViewController.didMoveToParentViewController(self)
	}
	
	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
		// Dispose of any resources that can be recreated.
	}
	
}
```

ここで親コントローラーが子コントローラーを自分のプロパティーにして、子コントローラーの時と同じく `loadView()` で自分のビューを `baaseView` に入れ替えます。さらに `viewDidLoad()` では、`ChildViewController` を自分の子コントローラーとして追加した後、`ChildViewController.view` を `Subview` として自分の `baseView` の `subview` に指定します。これが終わったら、手動で子コントローラーの `didMoveToParentViewController(_:)` を呼び出します。これ重要。

これで全てのユーザの操作制御を一つのコントローラーに集中させずに、分散して作ることができます：
![MVC.png](https://qiita-image-store.s3.amazonaws.com/0/63998/4d88bae6-b65c-fd5e-2118-5a091210bf91.png)
（なんだか設計がすっごい複雑になった気がしますが…）

ちなみにサンプルプロジェクトも[Github](https://github.com/el-hoshino/ContainerViewControllerSample)に上げておきましたのでよろしければ参照してみてください。

そしてもし何か不備とか質問とかありましたらコメントで教えていただけると嬉しいです☺️

参考：
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html