xib で作った Custom View / ViewController を Storyboard とコードの両方から扱う

まとめ

Custom View / Custom ViewController どちらの場合も、 Storyboard とコードの両方から扱うためには、 View や ViewController そのものを xib で定義するのではなくて root view を xib で定義してinstantiate するのが良さそうです。

Custom View

まず MyView.xib を作成し、ルートの view の下に UI 部品を配置していきます。 このとき、ルートの view は MyView クラスではなく UIView のままです。

次に、 File's Owner に MyView クラスを設定します。 すると MyView.xib と MyView クラスが紐付けられ、 UI 部品の Outlet や Action を接続できるようになります。

f:id:takasfz:20180806175123p:plain

MyView クラスでは、ルートになる UIView を MyView.xib から生成し、 addSubview します。 Storyboard から生成された場合 ( init(coder:) ) とコードから生成された場合 ( init(frame:) ) のどちらで初期化された場合でも 同じように処理されるようにします。

import Foundation
import UIKit

class MyView: UIView {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        instantiateView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        instantiateView()
    }

    private func instantiateView() {
        // MyView.xib からインスタンスを生成し、 IBOutlet / IBAction を self と接続する
        let nib = UINib(nibName: "MyView", bundle: .main)
        let rootView = nib.instantiate(withOwner: self).first as! UIView

        // 生成した rootView の位置とサイズを self にぴったり合わせて addSubview
        rootView.frame = self.bounds
        self.addSubview(rootView)
    }

    @IBOutlet weak var label: UILabel!

    @IBAction func buttonDidTouch(_ sender: Any) {
        // do something
    }
}

Custom ViewController

MyViewController.xib を作成し、ルートの view の下に UI 部品を配置していきます。 ルートを UIViewController ではなく UIView にするのがポイントです。

次に、 File's Owner に MyViewController クラスを設定します。 すると MyViewController.xib と MyViewController クラスが紐付けられ、 UI 部品の Outlet や Action を接続できるようになります。

f:id:takasfz:20180806175126p:plain

MyViewController クラスでは、 loadView() をオーバーライドして、 MyViewController.xib から生成した UIView を self.view に設定します。

import Foundation
import UIKit

class MyViewController: UIViewController {

    override func loadView() {
        // MyViewController.xib からインスタンスを生成し root view に設定する
        let nib = UINib(nibName: "MyViewController", bundle: .main)
        self.view = nib.instantiate(withOwner: self).first as! UIView
    }

    @IBOutlet weak var label: UILabel!

    @IBAction func buttonDidTouch(_ sender: Any) {
        // do something
    }
}

出展: