@IBInspectableでプロパティに列挙型を使いたい
@IBDesignableと@IBInspectableとは
アプリを作る際にUIViewやNSViewを拡張してカスタムViewを作るのは良くあることだ。作成したカスタムViewの使い方は簡単で、XcodeのStoryboardで使用する場合はViewを配置してインスペクタでClassを作成したカスタムViewにするだけで良い。
が、カスタムViewを作る際にそのままUIViewのSubclassとして作ってしまうとStoryboard上でUIが何も表示されずに真っ白になってしまう。
これをカスタムViewの内容も表示されるようにするのが@IBDesignableで、インスペクタでプロパティの値の設定をできるようにするのが@IBInspectableだ。
@IBDesignableでカスタムViewの内容を表示する
@IBDesignableを使用しない場合のカスタムViewはStoryboardで下記のように表示される。
真っ白な状態で選択するとアンカーが表示されるだけで、正直見やすいとは言い難い。
が、@IBDesignableを使えば以下のようにカスタムViewの内容も表示されるようになる。(メーターのメモリが表示されているだけなのでサンプルとしては不適当だが…)
この@IBDesignableは以下のように作成したカスタムViewのclassの前に挿入して使用する。
@IBDesignable class GPLevelMeterView: UIView
これだけでXcodeのStoryboardでカスタムViewの内容も表示されるようになるのだ。
@IBInspectableでインスペクタで値を設定できるようにする
StoryboardではUIパーツを選択するとプロパティの値をインスペクタで設定できる。カスタムViewでも親クラスのプロパティの値はインスペクタに表示されるので設定することができるが、カスタムView独自のプロパティはインスペクタに表示されない。
これをインスペクタに表示して設定できるようにするのが@IBInspectableだ。
カスタムViewのプロパティ宣言に@IBInspectableを挿入すればそのプロパティがインスペクタに表示される。
@IBInspectable var zeroCenter: Bool = false
そしてこのプロパティzeroCenterは下記のようにインスペクタに表示される。
ただし、気を付けなくてはいけないのは@Inspectableで扱えるプロパティの型。Appleのドキュメントによると
You can add the IBInspectable attribute to any property in a class declaration, class extension, or category of type: boolean, integer or floating point number, string, localized string, rectangle, point, size, color, range, and nil.
となっている。
実際に使えるのは以下の通り。
- Bool
- Int
- Double
- Float
- String
- CGRect
- CGPoint
- CGSize
- UIColor
- UIImage
実はドキュメントにないUIImageもインスペクタに表示される。
またドキュメントにあるrangeはNSRangeを@IBInspectableにするとインスペクタにはフォームが表示されるがDesignables Build failedとなってしまいカスタムビューが表示されない。
コードの方でもProperty cannot be marked @IBInspectable because its type cannot be represented in Objective-Cとエラーが表示される。
列挙型(enum)を@IBInspectableにしたい場合
@IBInspectableを使う場合に問題になるのがプロパティが列挙型(enum)で宣言された型だった場合。例えば以下のコードはエラーになってしまう。
enum ScalePosition: Int {
case none = 0, left, top, right, bottom
}
@IBInspectable var scalePosition: ScalePosition = .none
@IBInspectableはScalePosition型には対応してないのが原因だ。
これを回避するには@IBInspectable用に別のプロパティを加える方法がある。
enum ScalePosition: Int {
case none = 0, left, top, right, bottom
}
var scalePosition: ScalePosition = .none
@available(*, unavailable, message: "This property is reserved for IB. Use 'scalePosition' instead.", renamed: "scalePosition")
@IBInspectable var scalePositionValue: Int {
get {
return self.scalePosition.rawValue
}
set(newValue) {
self.scalePosition = ScalePosition(rawValue: newValue) ?? .none
}
}
scalePositionValueは列挙型で宣言されているscalePositionを@IBInspectableが扱えるIntに変換しているだけ。
これでインスペクタにはscalePositionValueが表示されて値を設定できるようになる。
ちなみに@availableを使うことでコードからscalePositionValueにアクセスしようとした場合にscalePositionを使うようメッセージを出している。renamedでscalePositionを指定すればFixボタンで修正をすることもできる。
まとめ
StoryboardでカスタムViewの内容を確認しながら配置できるようになる@IBDesignableとそのプロパティをインスペクタで設定できるようにする@IBInspectableは、自分でカスタムViewを作ったことがある人なら一度は書いたことのあるコードだろう。
今回は列挙型(enum)を@IBInspectableにしてインスペクタでポップアップメニューを使って選択できる形にしたくて調べてみたのだが、結局ポップアップメニューでの選択の方法は見つからなかった。
列挙型を使うためにcomputedプロパティを使う方法は下記で見つけた。
今回作った@IBDesignableと@IBInspectableを使ったカスタムビューのサンプルはGitHubに置いてある。
Discussion