@IBDesignable + α でInterface Builderとコードを仲良くさせる


Interface Builderはコーディング不要でUI実装ができて便利ですが、基本的には文字サイズや色などのパラメータの具体的な値を直接指定する必要があるため、
複数の要素に対して設定されている値が変更になった場合には、それらをすべてを修正せねばならず、非常に煩雑です。
かといって、修正に強くするためにパラメータをすべてコードから指定するようにすると、xxx.textColor = myColorxxx.font = myBoldFontといったコードが量産され、ソースは長くなるわコーディングが苦しいだけの単調作業になるわであまりやりたくありません。

そこで、Interface Builderとコードによるパラメータ指定のいいとこどりをしたUI実装方法を試してみたのでご紹介します。

@IBDesignableと@IBInspectable

今回の方法では、@IBDesignable@IBInspectableを使います。
@IBDesignableと@IBInspectableを使えば、角丸やボーダーの追加など、通常はInterface Builderから設定できない項目も設定できるようになり、さらに結果が即時反映されるため、Interface Builderがより強力になります。

基本的な使い方は公式ドキュメント(下記)や様々な開発者の方のサイトにて解説されていますのでそちらをご参照ください。

Creating a Custom View That Renders in Interface Builder

 

実装してみる

今回のサンプルでは、UILabelの文字色とフォントサイズを設定できることをゴールとします。
とはいえやることはあまり多くありません。
最終的なソースファイルは以下のとおりで、順次解説します。

@IBDesignable class DesignableLabel: UILabel { 
    static let fonts: [String: CGFloat] = [
        "Tiny": 10,
        "Small": 14,
        "Regular": 17,
        "Large": 24
    ]
    
    static let colors: [String: UIColor] = [
        "Mono01": UIColor.blackColor(),
        "Mono02": UIColor.grayColor(),
        "Color01": UIColor.redColor(),
        "Color02": UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
    ]    

    @IBInspectable var fontId: String = "Regular"
    @IBInspectable var colorId: String = "Mono01"

    override func drawRect(rect: CGRect) {
        
        let fontSize = DesignableLabel.fonts[fontId] ?? 100
        self.font = UIFont.systemFontOfSize(fontSize)
        
        self.textColor = DesignableLabel.colors[colorId] ?? UIColor.blackColor()
        
        super.drawRect(rect)
        
    }
}

●クラス

@IBDesignable修飾子をつけたUILabelのサブクラスとして実装します。

余談ですが、Extensionを使って実装できれば、Custom Classの指定も不要になり理想的だったのですが、
試してみたところExtensionを使った場合は変更がInterface Builder上に即時反映されなかったので(アプリをRunすれば設定は反映されていました)、このように実装しています。

●定数 fonts, colors

フォントサイズと文字色のパターンはDictionary型の定数として定義します。
Dictionaryの各値のキーと、Interface Builderで入力された値とのマッチングを行うことであらかじめ設定された値を取得できるようにしています。

●変数 fontId, colorId

Interface Builderから設定できるよう、@IBInspectable修飾子をつけて宣言します。前述のとおり、Dictionary型の定数を経由して実際のフォントサイズ等を取得するため、これらの変数はString型で宣言します。

●メソッド drawRect:

fontId, colorIdに入力された各値をもとに、それぞれフォントサイズ・文字色に変換し、ビューに反映させます。
IDは文字列で直接入力するので、存在しない値が入力された場合は明らかに異常な表示または差し障りのない表示にする、といった対処が必要になるかと思います。
この例では、フォントサイズについてはやたらでかくすることで異常な表示に、文字色については黒にすることで差し障りのない表示にしています。

試してみる

Interface Builder上にUILabelを配置し、Custom Classとして今回作成したクラスを選択すれば準備完了です。
Custom Classの設定
“Font Id”にTiny、”Color Id”にColor01と入力すると、ラベルの見た目が設定された値(フォントサイズ=10pt、文字色=赤)に応じて更新されます。
Font IdとColor Idを設定
また、”Font Id”に誤った文字(この例ではTinyy)を入力するとこのように巨大化してしまいます。
誤ったFont Idを設定
もちろん、クラスファイル側の設定を変更すれば、Interface Builder上の表示も即時更新されます(試してみたところ数秒程度はラグがあるようですが)。

まとめ

@IBDesignableと@IBInspectableをつかって、Interface BuilderからのID指定でビューの見た目を定義する方法をご紹介しました。
この方法を用いれば、アプリ全体で一貫性のあるUIを、比較的簡単に実装できます。

 

また、今回は簡単のために各種パラメータをstatic定数としてハードコーディングしていますが、別途マネージャクラスやplistで管理できるようになると、より使いやすくなるかと思います。

[2016/3/29追記]

plistからの読み込みは時間がかかるため、Interface Builderへの即時反映ができない模様です。