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

**カスタムビューを xib で定義しつつ、それを IBDesignable できる形で他の xib/Storyboard にロードして配置したい**、という場合、この記事のようにやればできます。
[Create an IBDesignable UIView subclass with code from an XIB file in Xcode](http://supereasyapps.com/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6)

## 1.

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

![](https://qiita-image-store.s3.amazonaws.com/0/3603/0ccc8d9e-f996-64f7-2fe5-c08e05f25810.png)

## 2.

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

```swift: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]))
	}
}
```

```swift: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` という表示になって見た目が反映される。

![](https://qiita-image-store.s3.amazonaws.com/0/3603/d6932166-b8e7-11c0-ac67-513bb2b17c0c.png)

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

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

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

----

古い記事：[カスタム UIView を xib から作る方法](http://qiita.com/usagimaru/items/73c8599847f17a7cbfbb)