XcodeのLive Renderingを使って簡単に使い勝手の良いアイコンボタンをつくろう

こんばんは。Xcode6になって様々な機能がXcodeに追加されました。その中で、作業的にかなりやりやすくなったLive Renderingについて書いてみたいと思います。とても便利なのにもしかしたらあまり使われていないのかもと思ったからです。Live Renderingとは、UIViewで書いた内容がInterfaceBuilder上にリアルタイムに反映されるというとても便利な機能です。入り込みやすいようにチュートリアル形式にしました。

## Live Renderingを利用することでの変化

今まではカスタムのViewをInterfaceBuilderで表示する際、中身がリアルタイムに描画されないので、下記のようになってしまっていました。もしくは、Viewに色を付けずタップしないとどこにカスタムのビューがあるかわからないような状況が発生していました。

![morizotter_blogview.png](https://qiita-image-store.s3.amazonaws.com/0/24041/1e22d998-1bcb-517b-b160-022e462f0be4.png "morizotter_blogview.png")

それがLive Renderingを利用することで、このように実際に端末に表示された時のようにアイコンを見ることができるようになり、より一歩踏み込んだビューの作成ができると思います

![morizotter_blogview21.png](https://qiita-image-store.s3.amazonaws.com/0/24041/1289b11d-945a-79f3-335e-3c3c00c37fe7.png "morizotter_blogview21.png")


それでは実際にLive Renderingを利用したボタンを作ってみたいと思います。

## 簡単にアイコンボタンを作る

UIControl（UIView+イベントハンドリングができる）を継承したクラスを作成します。そして、下記のコードを書き込みます。 **@IBDesignable** をクラス宣言の前に書くことで、Live Renderingが有効になります。 **@IBInspectable** をインスタンス変数の前に書くことで、InterfaceBuilderから値を変更することができるようになります。ここで、

```Swift
@IBDesignable
class IconButton: UIControl {
    
    @IBInspectable var iconImage: UIImage?
    
    override func drawRect(rect: CGRect) {
        
        if let iconImage = self.iconImage {
            iconImage.drawInRect(self.bounds)
            
            let maskPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: CGRectGetWidth(self.bounds) / 2)
            let maskLayer = CAShapeLayer()
            maskLayer.path = maskPath.CGPath
            self.layer.mask = maskLayer;
        }
    }
}
```

InterfaceBuilderを開き、UIViewを画面に追加します。そして、クラスを作成したIconButtonに変更します。

![morizotter_blogclass.png](https://qiita-image-store.s3.amazonaws.com/0/24041/50f11346-a3e6-f78d-97c2-ba252d99ca28.png "morizotter_blogclass.png")

すると、InterfaceBuilderでこのような表示を見ることができます。IconButtonで設定したiconImageがInterfaceBuilderから変更できるようになっています。

![morizotter_blogicon-image.png](https://qiita-image-store.s3.amazonaws.com/0/24041/c5e350e1-8cbe-e3d3-9109-e6b38c168449.png "morizotter_blogicon-image.png")

今回は下記の画像を利用することにしました。

![morizotter_blogIcon@2x.png](https://qiita-image-store.s3.amazonaws.com/0/24041/886e6fde-626a-9b80-c59f-ea6ba7689110.png "morizotter_blogIcon@2x.png")

今の時点でInterfaceBuilderはこのようになっています。

![morizotter_blognow.png](https://qiita-image-store.s3.amazonaws.com/0/24041/c7c64ac4-fc96-37a2-95ad-f07fcb871e8d.png "morizotter_blognow.png")


*ここまでのコードは、[こちら](https://github.com/morizotter/MZRLiveRenderingIconButtonSample/releases/tag/0.1)を御覧ください*

## ブラッシュアップ

ちょっとさみしいので、枠線を追加したいと思います。

```Swift
@IBDesignable
class IconButton: UIControl {
    
    @IBInspectable var iconImage: UIImage?
    @IBInspectable var borderColor: UIColor = UIColor.clearColor()
    @IBInspectable var lineWidth: CGFloat = 0.0
    var borderLayer: CAShapeLayer?
    
    override func drawRect(rect: CGRect) {
        
        if let iconImage = self.iconImage {
            iconImage.drawInRect(self.bounds)
        }
        
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: CGRectGetWidth(self.bounds) / 2)
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.CGPath
        self.layer.mask = maskLayer;
        
        if self.borderLayer == nil {
            self.borderLayer = CAShapeLayer()
            self.borderLayer!.path = path.CGPath
            self.borderLayer!.fillColor = UIColor.clearColor().CGColor
            self.layer.addSublayer(self.borderLayer!)
        }
        self.borderLayer!.strokeColor = self.borderColor.CGColor
        self.borderLayer!.lineWidth = self.lineWidth
        self.borderLayer!.lineCap = kCALineCapRound
    }
}
```


<img style="display:block; margin-left:auto; margin-right:auto;" src="http://blog.morizotter.com/wp-content/uploads/2014/12/morizotter_blogview2.png" alt="View2" title="view2.png" border="0" width="600" height="421" />

attributes Inspectorはこのようになっています。

![morizotter_blogattributes.png](https://qiita-image-store.s3.amazonaws.com/0/24041/a974279c-4a77-53ba-1979-645b42f4b8c5.png "morizotter_blogattributes.png")

ボタンの要素を幾つか追加して簡単にアイコン・ボタンを作ることが出来ました。

更に、アクションやhighlightedの時の変化などを付け加えて、より使い勝手の良いボタンにしました。

```Swift
@IBDesignable
class IconButton: UIControl {
    
    @IBInspectable var normalImage: UIImage? {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    @IBInspectable var highlightedImage: UIImage? {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    @IBInspectable var normalBorderColor: UIColor = UIColor.clearColor() {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    @IBInspectable var highlightedBorderColor: UIColor = UIColor.clearColor() {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    @IBInspectable var lineWidth: CGFloat = 0.0 {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    lazy var maskLayer: CAShapeLayer = {
        return CAShapeLayer()
    }()
    
    lazy var borderLayer: CAShapeLayer = {
        let borderLayer = CAShapeLayer()
        borderLayer.fillColor = UIColor.clearColor().CGColor
        self.layer.addSublayer(borderLayer)
        return borderLayer
    }()
    
    var circlePath: UIBezierPath {
        get {
            return UIBezierPath(roundedRect: self.bounds, cornerRadius: CGRectGetWidth(self.bounds) / 2)
        }
    }
    
    override var highlighted: Bool {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    override func drawRect(rect: CGRect) {
        
        var iconImage: UIImage?
        var borderColor = UIColor.clearColor()
        if self.highlighted {
            if let highlightedImage = self.highlightedImage {
                iconImage = highlightedImage
                borderColor = self.highlightedBorderColor
            }
        } else {
            if let normalImage = self.normalImage {
                iconImage = normalImage
                borderColor = self.normalBorderColor
            }
        }
        
        iconImage?.drawInRect(self.bounds)
        
        self.maskLayer.path = self.circlePath.CGPath
        self.layer.mask = self.maskLayer
        
        self.borderLayer.path = self.circlePath.CGPath
        self.borderLayer.strokeColor = borderColor.CGColor
        self.borderLayer.lineWidth = self.lineWidth
    }
}

```

```Swift
import UIKit

class ViewController: UIViewController {
    
    @IBAction func buttonTapped(sender: IconButton) {
        println("button tapped!")
    }
}
```

## おまけ：利用画像をベクターで

この時点で、どんな形に伸縮しても縦横が同じサイズであればその矩形の大きさのアイコンボタンを作ることが出来ました。ただし、現在、100px x 100pxの表示をするために2倍スケールの200px x 200px の画像を利用していています。このままではiPhone 6Plusのために別に3倍スケールの画像を用意する必要があります。別画像を用意するのではなく、1つの画像を用意するだけで3倍スケールにも対応できるようにvectorを利用しようと思います。

先ほどの画像ファイルは元はIllustratorで作成したものでした。これをIllustratorで開きます。
![morizotter_blogillustrator.png](https://qiita-image-store.s3.amazonaws.com/0/24041/9ea3051c-4c3e-52cd-080d-e5b258b37011.png "morizotter_blogillustrator.png")

ファイル>複製を保存... を選択し、PDF形式で保存します。

![morizotter_blogillustrator-save.png](https://qiita-image-store.s3.amazonaws.com/0/24041/ca0c2132-efb5-78fb-f16c-4132fa2c0e48.png "morizotter_blogillustrator-save.png")

ダイアログにはこのように返答しました。

![morizotter_blogillustrator-dialog.png](https://qiita-image-store.s3.amazonaws.com/0/24041/859a845d-61dc-2060-972a-463b642e5875.png "morizotter_blogillustrator-dialog.png")

ImageAssetsのTypesをVectorsに変更します。

![morizotter_blogassets.png](https://qiita-image-store.s3.amazonaws.com/0/24041/0a0a3ffb-1b72-242d-4405-ed8886b93dcb.png "morizotter_blogassets.png")

これでVector画像を扱えるようになりました。今回は100px x 100px のpdfを書き出してセットしています。ビルドをする際にXcodeがscale毎のビットマップ画像（@1x, @2x, @3x）を出力してくれるということです。

Live Renderingを利用することで、開発が更に楽しくなると思っています！ちなみに最後になりましたが、この機能はObjective-Cでも利用できます。

## サンプルコード

一通り実装したサンプルコードはこちらになります。

- [morizotter/MZRLiveRenderingIconButtonSample](https://github.com/morizotter/MZRLiveRenderingIconButtonSample)

## 参考

- [Interface Builder Object and Media Help: Creating a Custom View That Renders in Interface Builder](https://developer.apple.com/library/ios/recipes/xcode_help-IB_objects_media/chapters/CreatingaLiveViewofaCustomObject.html#//apple_ref/doc/uid/TP40014224-CH41-SW1)
- [UIBezierPathで角丸にする - Morizotter Blog](http://blog.morizotter.com/2014/11/01/rounded-corner-with-uibezierpath/)
- [Using Vector Images in Xcode 6](http://martiancraft.com/blog/2014/09/vector-images-xcode6/)
- [Xcode 6 時代のマルチデバイス対応 ～Size Classとベクター画像～ - Over&Out その後](http://d.hatena.ne.jp/shu223/20141002/1412209257)