カスタムビューを 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 からの初期化用イニシャライザどちらからも行うようにする。
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]))
}
}
// 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)
// "bundle: nil" はメインバンドルを指す
UINib(nibName: "MyView", bundle: NSBundle(forClass: self.dynamicType))…