Interface builderでカスタムViewのライブレンダリング



<blockquote class="twitter-tweet" lang="en"><p>今日のスライドです <a href="https://t.co/xIkI9IzEFZ">https://t.co/xIkI9IzEFZ</a> <a href="https://twitter.com/hashtag/potatotips?src=hash">#potatotips</a></p>&mdash; yomoapp(本人) (@yomoapp) <a href="https://twitter.com/yomoapp/status/514805382858809344">September 24, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


[【第9回】potatotips](https://github.com/potatotips/potatotips/wiki/potatotips-9)@[Fablic](http://jp.techcrunch.com/2014/09/25/jp20140924fril/)で話した内容です。
**Interface builder**(IB)について話しました。

# IBはXcode6からカスタムViewのライブレンダリングに対応しました

**Xcode5**の時代、iOSエンジニアは長方形の白いカスタムViewを見て、脳内レンダリングを行う必要がありました。

**Xcode6**からは、カスタムViewのレンダリング結果をIBの中で確認することができます。

![Screen Shot 2014-09-25 at 21.02.28.png](https://qiita-image-store.s3.amazonaws.com/0/1777/d5aa2450-cf78-5ba9-55e9-3dc69c5bfffa.png "Screen Shot 2014-09-25 at 21.02.28.png")


サンプルとして、どこにでもありそうなボタンを実装してみました。
標準ではないボタンのデザインをIBの中で確認できます。

# サンプルプロジェクト

**Xcode6**と **CocoaPods**が入っている方は、以下のコマンドでこのプロジェクトを動かすことができます。

```console
$ pod try HRButton
```

`Main.storyboard`を見るとライブレンダリングに対応していることが確認できます。

# ライブレンダリングに対応する

使うマクロは、`IB_DESIGNABLE`と`IBInspectable`のふたつです。

`IB_DESIGNABLE`はクラスの宣言の前に記述して、IBにレンダリングに対応しているカスタムViewであることを伝えます。

`IBInspectable`はPropertyの定義の中、ちょうど`IBOutlet`と同じ位置に記述します。
UIKit標準のView(UIButtonなど)と同じようにカスタムViewのPropertyもIB上で設定することができます。

```objc
#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface HRButton : UIControl

@property (nonatomic, strong) IBInspectable UIColor *highlightedTintColor;
@property (nonatomic, strong) IBInspectable NSString *title;
@property (nonatomic, strong) IBInspectable UIColor *titleColor;
@property (nonatomic) IBInspectable NSUInteger cornerRadius;

@end
```

`IBInspectable`を使うと、以下のようにインスペクタ経由で設定できるようになります。

![Screen Shot 2014-09-25 at 21.21.50.png](https://qiita-image-store.s3.amazonaws.com/0/1777/98a6125c-9ffa-bbbb-0fa2-317ebda88ac8.png "Screen Shot 2014-09-25 at 21.21.50.png")

インスペクタを操作すると、リアルタイムで更新されることが確認できると思います。

# 強力な機能です
1px単位の調整をどれだけ繰り返すことができるかというのはとても重要で、アプリの品質に効いてくる要素だと思っています。

この繰り返しのサイクルが、「修正 > ビルド > 確認」から「修正 > 確認」になるということは、大きなインパクトがあります。

![Screen Shot 2014-09-25 at 21.37.29.png](https://qiita-image-store.s3.amazonaws.com/0/1777/39df9626-773b-75dc-6a3e-062645d78141.png "Screen Shot 2014-09-25 at 21.37.29.png")

# Debug

普通の環境とは異なる状態で実行されるのでDebugの方法が異なります。

- カスタムViewのメソッドの中にBreakPointを仕込む
- IBを開いて対象のカスタムViewを選択
- Editor > Debug Selected Viewを実行

これで、`lldb`を使ったDebugを行うことができます。

# 実行環境

さて、`lldb`を使えるようになったところで、どんな環境でカスタムViewが実行され表示されているのかを調べてみます。  

```
(lldb) po [[UIDevice currentDevice] model]
iPhone Simulator
```

端末をiPhone Simulatorと認識しているようです。

``
(lldb) po self.superview
<UIWindow: 0x7fef03052cb0; frame = (0 0; 277 46); hidden = YES; autoresize = W+H;...
```
superviewがUIWindowでした。UIWindowのサイズはカスタムViewと同じです。

BreakPointを`drawRect:`の中に置いてbacktraceを見てます。

```
(lldb) thread backtrace all
  * frame #0: 0x000000021e7db3cc HRButton`-[HRButton drawRect:](self=0x00007fef0054e2c0, _cmd=0x0000000114bef273, rect=CGRect at 0x00007fff5063bbb0) + 28 at HRButton.m:73
    　-- 略 --
    frame #6: 0x000000011152b040 QuartzCore`-[CALayer renderInContext:] + 151
    frame #7: 0x000000010f5d53e1 IBDesignablesAgentCocoaTouch`-[IBCoreGraphicsViewRenderer createBitmapInfo] + 1141
    frame #8: 0x000000010f5c49a4 IBDesignablesAgentCocoaTouch`IBImageDataForViewWithScaleFactor + 136
    　-- 略 --
    frame #29: 0x000000010f5fafe0 IBDesignablesAgentCocoaTouch`main + 34
    frame #30: 0x000000011647e145 libdyld.dylib`start + 1
```

- アプリ自体をビルドしているわけではない
- Bitmapとして書き出した結果をIBに表示している

というようなことが分かります。

この実装から、今のところ次のような制限があります。

- `AppDelegate`は呼ばれないので、`UIAppearance`で定義したスタイルはIB上では反映されません
- 書き出した画像をIBで表示しているだけで、生きたViewがIB上に配置されるわけではない
  - 非同期で`addSubView`したり、アニメーション付きで表示されるViewはそのままでは表示されない

`TARGET_INTERFACE_BUILDER`や`prepareForInterfaceBuilder`を使うと表示されるようになります。[ドキュメント](https://developer.apple.com/library/ios/recipes/xcode_help-IB_objects_media/chapters/CreatingaLiveViewofaCustomObject.html#//apple_ref/doc/uid/TP40014224-CH41-SW1)を参考にしてください。

個人的にはライブレンダリングは効率化よく実装するためのものなので、既存のカスタムViewを完璧に対応させる必要はないと思っています。

# 話さなかったこと
検証できなかった、微妙だったなどの理由で話さなかったこと。

## ライフサイクル
以下のようなライフサイクルでカスタムViewがレンダリングされている（と認識しています）

- カスタムVIewに変更があると、`IBDesignablesAgentCocoaTouch`に組み込まれてビルドされる
- iPhone Simulatorのような環境で実行される(開発者には見えない)
- `initWithFrame:`で初期化
- 小さなUIWindowにaddSubview
- レンダリング結果を画像化
- IB上に画像を表示

## initWithFrameに対応する必要あり
IBで表示するのに`initWithCoder`ではなく、`initWithFrame`です。
実行時には`initWithCoder`が呼ばれるのでどちらで呼ばれても動作するように実装しなければいけません。

## カテゴリでも使える
例えば、すべてのUIViewにdebugオプションをつけるというようなことができます。
任意のViewにIB上で枠線をつけたりとか、そんな感じのことができるようになるのですが、非同期処理が難しかったり、カテゴリでaddSubviewのイベントを取得するのが難しかったりで、あまり役にたつような物を作れませんでした。


# まとめ
ライブレンダリングを使おう！