[iOS swift] ContainerViewをプログラムで作りたい

2016/04/25

こんにちは。きんくまです。

今回はContainerViewです。
ContainerViewを使うと親子関係を持つViewControllerをInterface Builder上で簡単に設定できます。

こういうUIのやつ

containerView1

IB上ではこう表示されます。

containerView2

ただ、IB上でこのように設定してしまうと、子供側のStoryboardの再利用が難しい気がしました。
基本的にStoryboardは分割しておいた方が管理が楽なので、子供は子供だけのStoryboardファイルにして、それを親側に埋め込みたいです。
それで、いろいろと試行錯誤していて、なんとかわかったのでまとめておこうかと。

詳しいことはAppleさんのドキュメントを見てください。
>> Implementing a Container View Controller

StoryboardではContainerViewは使わずにUIViewを使う

Storyboard上のContainerViewというのは、UIContainerViewという特別なクラスがあるわけではなく、普通のUIViewのようです。
なので、親側で子供のViewを埋め込むのはUIViewにします。

今回は 親、子供、孫の3段階の親子関係を持つようにするサンプルを作りました。

親です。
親はUINavigationControllerの中に埋め込まれている設定にしました。実際のアプリはほとんどこれに埋め込まれていることが多いと思ってそうしています。

親には上下2つのContainerView(実際にはUIView)を配置しました。

containerView3

子供です。
子供には左右2つのContainerView(実際にはUIView)を配置しました。

containerView4

孫です。
これがコンテンツとなります。TL = TopLeft, TR = TopRight, BL = BottomLeft, BR = BottomRightです。
四隅のテキストには外枠に対して1pxのマージンを設けてあります。

containerView5

完成図です。親の中に子供、子供の中に孫を入れるとこうなります。
子供の背景色を半透明にして、孫の背景色をなしにしているので、色が混じって見えます。

containerView6

完成図を横向きにした場合です。

containerView7

それぞれのStoryboardは独立していますので、入れ子関係の組み合わせはあとでわりと簡単に変更することができます。
たとえば、孫のViewControllerを親に直接入れる2階層にするとか。

コードをみてみる

さきほどのAppleさんのドキュメントを見ると、ContainerViewのように親子関係をもたせるにはこんな感じにやればいいようです。

    func displayContentController(content:UIViewController, container:UIView){
        addChildViewController(content)
        content.view.frame = container.bounds
        container.addSubview(content.view)
        content.didMoveToParentViewController(self)
    }

もし、親子関係を解除する場合はこっち

    func hideContentController(content:UIViewController){
        content.willMoveToParentViewController(self)
        content.view.removeFromSuperview()
        content.removeFromParentViewController()
    }

このへんはそういう作法だと思うので、特に何も考えませんw

親のコード

class ViewController: UIViewController {
    @IBOutlet weak var topContainer:UIView!
    @IBOutlet weak var bottomContainer:UIView!
    
    var topHorizontalController:HorizontalViewController?
    var bottomHorizontalController:HorizontalViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "Hello Container"
        
        topHorizontalController = createHorizontalViewController("Top")
        displayContentController(topHorizontalController!, container: topContainer)
        
        bottomHorizontalController = createHorizontalViewController("Bottom")
        displayContentController(bottomHorizontalController!, container: bottomContainer)
    }
    
    func createHorizontalViewController(containerTitle:String)->HorizontalViewController{
        let sb:UIStoryboard = UIStoryboard(name: "HorizontalViewController", bundle: nil)
        let hController:HorizontalViewController = sb.instantiateInitialViewController() as! HorizontalViewController
        hController.containerTitle = containerTitle
        return hController
    }

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

    func displayContentController(content:UIViewController, container:UIView){
        addChildViewController(content)
        content.view.frame = container.bounds
        container.addSubview(content.view)
        content.didMoveToParentViewController(self)
    }
    
    func hideContentController(content:UIViewController){
        content.willMoveToParentViewController(self)
        content.view.removeFromSuperview()
        content.removeFromParentViewController()
    }
}

子供のコード

import UIKit

class HorizontalViewController: UIViewController {
    @IBOutlet weak var leftContainer:UIView!
    @IBOutlet weak var rightContainer:UIView!
    
    var leftSampleController:SampleViewController?
    var rightSampleController:SampleViewController?
    
    var containerTitle:String?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        leftSampleController = createSampleViewController("\(containerTitle!) Left")
        displayContentController(leftSampleController!, container: leftContainer)
        
        rightSampleController = createSampleViewController("\(containerTitle!) Right")
        displayContentController(rightSampleController!, container: rightContainer)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func createSampleViewController(viewTitle:String)->SampleViewController{
        let sb:UIStoryboard = UIStoryboard(name: "SampleViewController", bundle: nil)
        let sampleController:SampleViewController = sb.instantiateInitialViewController() as! SampleViewController
        sampleController.buttonTitle = viewTitle
        return sampleController
    }
    
    func displayContentController(content:UIViewController, container:UIView){
        addChildViewController(content)
        content.view.frame = container.bounds
        container.addSubview(content.view)
        content.didMoveToParentViewController(self)
    }
    
    func hideContentController(content:UIViewController){
        content.willMoveToParentViewController(self)
        content.view.removeFromSuperview()
        content.removeFromParentViewController()
    }
}

孫のコード

class SampleViewController: UIViewController {

    @IBOutlet weak var centerButton:UIButton!
    var buttonTitle:String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        if buttonTitle != nil {
            setCenterButtonLabel(buttonTitle!)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func setCenterButtonLabel(text:String){
        centerButton.setTitle(text, forState: UIControlState.Normal)
    }
}

ファイルの階層構造

containerView8

まとめ

分割管理できて、再利用できるようになったのでうれしいです。
あとどうしてなのかわからないのですが、デバイスを横向きにしても四隅がぴったりはまっているので、AutoLayoutも設定されているっぽいです。

>> 今回のプロジェクト一式データです。


自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る