LoginSignup
49
41

More than 5 years have passed since last update.

IBDesignable できるカスタムビューを xib で定義するときには NSBundle に注意する

Last updated at Posted at 2016-05-26

カスタムビューを xib で定義しつつ、それを IBDesignable できる形で他の xib/Storyboard にロードして配置したい、という場合、この記事のようにやればできます。
Create an IBDesignable UIView subclass with code from an XIB file in Xcode

1.

カスタムビューを xib で作って、"File's Owner" のクラスにクラス名を指定する。

2.

カスタムビューのクラス(File's Owner に指定したクラス)を実装する。
nib からビューをロードして addSubview() するのだが、このとき、コードからの初期化用イニシャライザ init(frame: CGRect) と nib からの初期化用イニシャライザどちらからも行うようにする。

MyView.swift/Swift2
import UIKit

@IBDesignable class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadFromNib()
    }

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

    private func loadFromNib() {
        // ここは UINib を使っても良い
        let v = NSBundle(forClass: self.dynamicType).loadNibNamed("MyView", owner: self, options: nil).first as! UIView
        addSubview(v)

        v.translatesAutoresizingMaskIntoConstraints = false
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view]-0-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: ["view" : v]))
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view]-0-|",
            options: NSLayoutFormatOptions(rawValue: 0),
            metrics: nil,
            views: ["view" : v]))
    }
}
MyView.swift#loadFromNib()
// Swift3

let v = Bundle(for: type(of: self)).loadNibNamed("MyView", owner: self, options: nil)?.first as! UIView
addSubview(v)


// Case1. SnapKit
v.snp.makeConstraints { (maker: ConstraintMaker) in
    maker.edges.equalTo(self)
}

// Case2. NSLayoutConstraint
v.translatesAutoresizingMaskIntoConstraints = false
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]-0-|",
                                              options: NSLayoutFormatOptions(rawValue: 0),
                                              metrics: nil,
                                              views: ["view" : v]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view]-0-|",
                                              options: NSLayoutFormatOptions(rawValue: 0),
                                              metrics: nil,
                                              views: ["view" : v]))

3.

Storyboard など他の nib に UIView のステンシルを配置したら、それのクラス名を 2 で作ったクラスにする(今回だと MyView)。うまくいけばこれでビルドが自動で走って Designables Up to date という表示になって見た目が反映される。

注意:NSBundle は mainBundle() で初期化しない

mainBundle() は現在実行中のアプリケーションのバンドルを返却するメソッドですが、IBDesignable はターゲットのアプリではなく IBDesignablesAgentCocoaTouch という Xcode のヘルパープログラム的なもので動いているので、mainBundle() で得られるバンドルとは異なるインスタンスになっているのだと思われます。(極端な話、Interface Builder 自体が iOS シミュレーターのようなものです。)なのでその代わりにクラス名からバンドルのインスタンスを得るメソッドを使用します。self.dynamicType は Objective-C でいう [self class] に相当するものです。

正しいバンドルが得られる
NSBundle(forClass: self.dynamicType)
UINibを使う場合
// "bundle: nil" はメインバンドルを指す
UINib(nibName: "MyView", bundle: NSBundle(forClass: self.dynamicType))

古い記事:カスタム UIView を xib から作る方法

49
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
41