@IBDesignableと@IBInspectableを使ってグラデーション可能なカスタムViewを作ってみた

## リアルタイムに描画結果を確認
Xcode6で追加されたInterfaceBuilder（以下IB）の新しい機能であるIBDesignableとIBInspectableを使ってみました。

特徴としては細かなパラメータを設定できて、それを画面上で確認できることです。

以下の画像はIBDesignableを利用した円形のグラデーションをレンダリングするカスタムViewを使用して、IB上でパラメータを調整して作ったものです。

![スクリーンショット 2015-01-29 13.31.01.png](https://qiita-image-store.s3.amazonaws.com/0/45525/80a1884d-35d1-4a4f-3373-ae269bd68dab.png)

グラデーションの色、グラデーションの開始地点などをIBで設定しています。[GitHubにプロジェクトファイルをあげています。](https://github.com/mnat44/Inspectable)

Autolayout対応がマストになった今、いろいろなサイズでの見た目を簡単に調整できた方が楽ですよね。

以前はこういったカスタムViewの細かな調整はコードを修正しながら行っていましたが、ちょっと工夫すればIB上で簡単に変更できるようになります。

### `@IBDesignable`を利用したグラデーションビュー

IB上でグラデーションの調整をできるようにした`GradationView`のソースです。

```swift
@IBDesignable class GradationView: UIView {

    @IBInspectable var gradationStartColor : UIColor = UIColor.whiteColor()
    @IBInspectable var gradationEndColor : UIColor = UIColor.blackColor()
    
    @IBInspectable var gradationXPosition : CGFloat = 0.5
    @IBInspectable var gradationYPosition : CGFloat = 0.5
    @IBInspectable var circleSize : CGFloat = 1.0

    override func drawRect(rect: CGRect) {
        
        var startRed: CGFloat = 0
        var startGreen: CGFloat = 0
        var startBlue: CGFloat = 0
        var startAlpha: CGFloat = 0
        self.gradationStartColor.getRed(&startRed, green: &startGreen, blue: &startBlue, alpha: &startAlpha)
        
        var endRed: CGFloat = 0
        var endGreen: CGFloat = 0
        var endBlue: CGFloat = 0
        var endAlpha: CGFloat = 0
        self.gradationEndColor.getRed(&endRed, green: &endGreen, blue: &endBlue, alpha: &endAlpha)
        
        var context = UIGraphicsGetCurrentContext()
        CGContextSaveGState(context)
        
        CGContextAddEllipseInRect(context, self.frame)
        
        var colorSpaceRef:CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()
        var components:[CGFloat] = [
            startRed, startGreen, startBlue, startAlpha,
            endRed, endGreen, endBlue, endAlpha,
        ]
        
        var locations:[CGFloat] = [ 0.0, 1.0 ]
        let locationCount = UInt(locations.count)
        var gradientRef: CGGradientRef = CGGradientCreateWithColorComponents(colorSpaceRef, components, locations, locationCount)
        
        var frame:CGRect = self.bounds
        var radius:CGFloat = CGRectGetWidth(frame)
        
        var startCenter: CGPoint = frame.origin
        startCenter.x += frame.size.width  * self.gradationXPosition
        startCenter.y += frame.size.height * self.gradationYPosition
        
        var endCenter = startCenter
        
        var startRadius: CGFloat = 0.0
        var endRadius  : CGFloat = CGRectGetWidth(self.bounds) * self.circleSize
        
        CGContextDrawRadialGradient(context, gradientRef, startCenter, startRadius, endCenter, endRadius, CGGradientDrawingOptions(kCGGradientDrawsAfterEndLocation))
        
        CGContextRestoreGState(context)
    }
}
```

`@IBDesignable`をクラス名の前につけるとIB上で`@IBInspectable`をつけたプロパティの変更とその表示ができるようになります。

`@IBDesignable`はUIViewのサブクラスに、`@IBInspectable`はプロパティに設定します。`@IBInspectable`に設定できる型については後述します。


### ビルド時

`@IBDesignable`を設定したViewをStoryboard、あるいはxibに設定すると、identity inspectorの欄に"Designable"という項目が追加されます。

![ib_updating.png](https://qiita-image-store.s3.amazonaws.com/0/45525/9a75be90-a924-1ed7-f920-f90a78018482.png)

ステータスとしては更新中を示す`Updating`、更新済みを示す`Up to date`、ビルド失敗を示す`Build failed`の3つを確認しています。

![ib_upToDate.png](https://qiita-image-store.s3.amazonaws.com/0/45525/f6577957-3c84-bbba-5ee4-3b8682e2586b.png)

![ib_failed.png](https://qiita-image-store.s3.amazonaws.com/0/45525/be6d3411-7353-43ab-03ed-6c9bf99e7628.png)

ビルド失敗の場合はissue navigatorに警告が出ます。

![issue_nav.png](https://qiita-image-store.s3.amazonaws.com/0/45525/014b6fa6-bea0-bddd-668d-ff8dbe893870.png)




### `@IBInspectable`に指定できる型

`@IBInspectable`を使用できる型は決まっています。

* Bool
* Int
* CGFloat
* Double
* String
* CGPoint
* CGSize
* CGRect
* UIColor
* UIImage

設定するとIB上ではこのように表示されます。

![IBInspectable.png](https://qiita-image-store.s3.amazonaws.com/0/45525/3e2fc08f-3ce7-11cc-8722-ef594f50f763.png)

コード上で初期値として設定した値が代入されるわけではないようです。

`CGRect`や`CGSize`、`CGPoint`などの実数を指定する項目の数値のインクリメント、デクリメントが1.0ずつステップしてしまって困っているのですが設定方法がわかりません。しょうがないので実数を手入力しています。

### IB使用時のみ有効にする

IBだけで確認するためのメソッド、マクロも用意されています。
IBだけに反映させたいものがある場合は、`prepareForInterfaceBuilder()`メソッドを利用します。

```swift
func prepareForInterfaceBuilder()
```

マクロは`TARGET_INTERFACE_BUILDER`を使用します。

```swift
override func drawRect(rect: CGRect) {
    #if TARGET_INTERFACE_BUILDER
       // IBで作業時に描画させたいもの
    #endif
}

```

## まとめ

少しこったイメージのUIを作ろうとしたときはUIImageViewを利用すればできるのですが、修正を入れるたびに画像に手を加えてプロジェクト内に取り込む作業をしなければならないのが面倒です。`@IBDesignable`を使えばコードの修正とIBをちょっといじるだけでリアルタイムに結果を確認できるのはすごく便利ですね。

`@IBDesginable`を使うことでリアルタイムに確認ができるので、ビルドしては確認し、ビルドしては確認し、というプロセスを繰り返さなくても良くなります。`@IBDesignable`を使用したカスタムViewを作って、細かな設定はデザイナーさんにお任せしてしまう、というのもありかもしれません。


## メモ

[WWDC2014のWhat's New in Interface Builder](https://developer.apple.com/videos/wwdc/2014/#411)ではカスタムフレームワークを作成するように言っていたのですが、作らなくても使用可能でした。カスタムフレームワークについて知識不足なので、この点はもう少し調べて追記しようと思います。


## 参考

* https://developer.apple.com/videos/wwdc/2014/#411
* http://www.thinkandbuild.it/building-custom-ui-element-with-ibdesignable/
* http://safx-dev.blogspot.jp/2014/11/ibdesignableibinspectable.html
* http://dev.classmethod.jp/references/xcode-6-interface-builder-live-rendering/