#概要
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
- 要するに以下のような感じ
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 のビューと入れ替えます。
以上、参考までに。
以下大体のサンプルコードを記載する。
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()
}
}