検証環境:
Xcode 11.3.1
Swift 5.1.3
IBDesignable や IBInspectable を使うことで Storyboard や Xib 上でカスタムの属性の設定ができ、変更がリアルタイムに反映されてプレビューできるようになります。
ところで、IBOutlet で接続したビューに対して初期設定を行う場合には接続完了後のタイミングで呼ばれる UIView.awakeFromNib()
内でやるのがセオリーかと思いますが、 Storyboard や Xib 上のプレビュー時にはこのメソッドが呼ばれません。
その代わりに UIView.prepareForInterfaceBuilder()
が呼ばれるのでこちらにも同様の初期設定のコードを書いておくと実行時と同じような処理結果にすることができます。
プレビュー時とランタイム(実行)時で呼ばれるメソッドの違いについて以下の検証用コードを作って試してみました。
検証用コード
import UIKit @IBDesignable open class MyCustomView: UIView { var items: [String] = [] @IBInspectable open var prop1: String? { didSet { items.append("IBInspectable prop1") } } @IBInspectable open var prop2: Int = 0 { didSet { items.append("IBInspectable prop2") } } override public init(frame: CGRect) { super.init(frame: frame) items.append("init(frame:)") } required public init?(coder: NSCoder) { super.init(coder: coder) items.append("init?(coder:)") } override public func awakeFromNib() { super.awakeFromNib() items.append("awakeFromNib") } override public func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() items.append("prepareForInterfaceBuilder") } override open func updateConstraints() { super.updateConstraints() items.append("updateConstraints") } override public var intrinsicContentSize: CGSize { items.append("intrinsicContentSize") return super.intrinsicContentSize } override open func layoutSubviews() { super.layoutSubviews() items.append("layoutSubviews") configure() } private func configure() { guard subviews.count == 0 else { return } let textView = UITextView() textView.isEditable = false addSubview(textView) textView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: topAnchor), textView.bottomAnchor.constraint(equalTo: bottomAnchor), textView.leadingAnchor.constraint(equalTo: leadingAnchor), textView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) let result = items.enumerated().map { "\($0 + 1). \($1)" }.joined(separator: "\n") print(result) textView.text = result } }
Storyboard プレビュー時
1. init(frame:) 2. IBInspectable prop1 3. IBInspectable prop2 4. prepareForInterfaceBuilder 5. updateConstraints 6. layoutSubviews
まずinit?(coder:)
ではなく、init(frame:)
が呼ばれます。
そしてawakeFromNib
は呼ばれず、代わりにprepareForInterfaceBuilder
が呼ばれます。
AutoLayout を設定してますが、intrinsicContentSize
は呼ばれないようです。
ランタイム(実行)時
こちらは上記StoryBoardで設定したものが実際に実行された時の結果になります。
1. init?(coder:) 2. IBInspectable prop1 3. IBInspectable prop2 4. awakeFromNib 5. intrinsicContentSize 6. updateConstraints 7. layoutSubviews
コードからカスタムビュー生成(Frame指定)
以下はおまけで同じカスタムビューをコードで生成した場合も試してみました。
let myCustomView = MyCustomView(frame: .init(origin: .zero, size: .init(width: 150, height: 150))) myCustomView.prop1 = "Hello" myCustomView.prop2 = 100 view.addSubview(myCustomView)
結果
1. init(frame:) 2. IBInspectable prop1 3. IBInspectable prop2 4. updateConstraints 5. layoutSubviews
コードからカスタムビュー生成(AutoLayout指定)
let myCustomView = MyCustomView() myCustomView.prop1 = "Hello" myCustomView.prop2 = 100 view.addSubview(myCustomView) myCustomView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ myCustomView.topAnchor.constraint(equalTo: view.topAnchor), myCustomView.leadingAnchor.constraint(equalTo: view.leadingAnchor), myCustomView.trailingAnchor.constraint(equalTo: view.trailingAnchor), myCustomView.heightAnchor.constraint(equalToConstant: 150), ])
結果
1. init(frame:) 2. IBInspectable prop1 3. IBInspectable prop2 4. intrinsicContentSize 5. updateConstraints 6. layoutSubviews
関連
UICollectionViewCell や UICollectionReusableView に IBDesignable なカスタムビューを置いた場合、プレビュー時に layoutSubviews が呼ばれないケースがあるので注意。