概要
@IBDesignable
を指定したカスタム View クラスの中で UIColor
クラスのイニシャライザ init(named:)
を呼び出す処理を書いておき、Interface Builder 内で、ある View Controller クラスの View にそのカスタム View クラスを 追加したところ、以下のようなビルドエラーが発生しました。
IB Designables: Failed to render and update auto layout status for [View Controller のクラス名] ([Interface Builder 内での View Controller の Object ID]): The agent crashed
この問題の原因について調査したところ、 @IBDesignable
を指定したカスタム View クラスの prepareForInterfaceBuilder()
から呼び出される処理の中で Asset Catalog の内容を正しく読み出されないためであることがわかりました。
prepareForInterfaceBuilder()
から呼び出される処理の中でも Asset Catalog の内容を正しく読み取れるように設定することにより、 Asset Catalog 内で定義した色情報を使用できるようになりました。
エラーログの格納場所
本記事が対象とするエラーの詳細ログは ~/Library/Logs/DiagnosticReports
ディレクトリ内の IBDesignablesAgent-iOS_YYYY-MM-DD-HHMMDD_[マシン名].crash
に記録されています。
@IBDesignable
を指定したカスタム View の定義方法
@IBDesignable
を指定したカスタム View クラスを使用することにより、 Interface Builder 上でカスタム View のプレビューをするなどの利点があります。
@IBDesinable
を使用する利点や使用方法については、下記リンク先の記事が参考になります。
カスタムコンポーネントを使用したUI実装について - ZOZO Technologies TECH BLOG
prepareForInterfaceBuilder()
から Asset Catalog の内容を読み取る
prepareForInterfaceBuilder()
から呼び出される処理の中で UIColor
クラスのイニシャライザ init(named:)
を呼び出すとビルドエラーとなるのは、 init(named:)
では Asset Catalog の内容を正しく読み取ることができないためです。
Asset Catalog の内容を正しく読み取るためには init(named:)
の代わりに init(named:in:compatibleWith:)
を使って、 対象の Bundle を明示的に指定する必要があります。具体的なコードの例を下に示します。
@IBDesignable final class RoundedRectButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupAttributes()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupAttributes()
}
private func setupAttributes() {
clipsToBounds = true
layer.cornerRadius = 8.0
layer.borderWidth = 2.0
layer.borderColor = UIColor.black.cgColor
// この実装だとビルドエラー
backgroundColor = UIColor(named: "MyColor")!
}
}
@IBDesignable final class RoundedRectButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setupAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupAttributes()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupAttributes()
}
private func setupAttributes() {
clipsToBounds = true
layer.cornerRadius = 8.0
layer.borderWidth = 2.0
layer.borderColor = UIColor.black.cgColor
// この実装だとビルド成功
backgroundColor = UIColor(named: "MyColor", in: Bundle(for: type(of: self)), compatibleWith: nil)!
}
}
prepareForInterfaceBuilder()
から呼び出される処理の中では、 Asset Catalog 内の画像を使用する場合にも同様に、 UIImage
クラスのイニシャライザには init(named:)
ではなく init(named:in:with:)
を使う必要があります。
その他の対応方法
今回着目したビルドエラーは上記対応方法にて解決できるのですが、もしその他の理由により prepareForInterfaceBuilder()
内で実行する処理とそれ以外を区別したい場合には、 TARGET_INTERFACE_BUILDER
というプリプロセッサマクロが役に立ちます。
#if TARGET_INTERFACE_BUILDER
// prepareForInterfaceBuilder()内でのみ有効となる
#else
// prepareForInterfaceBuilder()内以外でのみ有効となる
#endif