LoginSignup
3
1

More than 3 years have passed since last update.

@IBDesignable を指定した View の中で Asset Catalog の内容を読み取るために必要なこと

Last updated at Posted at 2020-06-26

概要

@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 というプリプロセッサマクロが役に立ちます。

TARGET_INTERFACE_BUILDERの使用例
#if TARGET_INTERFACE_BUILDER
    // prepareForInterfaceBuilder()内でのみ有効となる
#else
    // prepareForInterfaceBuilder()内以外でのみ有効となる
#endif

参考情報

3
1
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
3
1