アイコンもPlaygroundで作っちゃおう

<img width="256" alt="icon256.png" src="https://qiita-image-store.s3.amazonaws.com/0/2191/218439af-3aee-30db-e1a9-991f32444704.png">

皆さん、アイコン作ってますか？

僕は「デザイン勉強してお洒落なの作ってやるぜ！」とか思ってた時期もありましたが、今はもう単色背景＋1文字でいいか、という感じになっています。
ガチなアプリだとそうはいかないかもしれませんが、個人アプリやハッカソン、TestFlight配布用の仮アイコンなどには十分です。

でもこうなってくると、もう、人が作るっていうレベルじゃないんですよね。色やテキスト、フォントとかを指定しすれば自動生成できちゃう。

実際、Ruby（RMagick）のスクリプト書いてやってたりしたんですが、せっかくならSwiftでやってしまおう、というのが今日のネタです。
（冒頭の画像も同じ方法で作りました。）

# それPlaygroundで

最初はコマンドラインツールを作ろうかとも考えましたが、

- リアルタイムにプレビューできる
- 慣れたiOSの環境で作業できる

ということから、Playgroundでやることにしました。
（`NSImage`より`UIImage`の方がいい…）

なお、Playgroundからはファイルシステムに自由にアクセスできませんが、Sandbox内は読み書きできるので、アイコンを作るぐらいなら不自由はありません。
※ [sketchytech/SwiftFiles](https://github.com/sketchytech/SwiftFiles) というUtilityもありましたが、今回は単純な処理しかしてないため使っていません。

# IconCreatorを作る

ではさっそく、`IconCreator`という雑なクラスを作ります。
`Sources`の中に入れてもいいですが、それだとインタラクティブさが損なわれるので、メインのファイル（実体は`Contents.swift`）にそのまま書くことにします。

## 定数、変数の定義

保存するディレクトリやアイコンの色、テキストなどの情報を保持します。

```swift
class IconCreator {
    let fileManager = NSFileManager.defaultManager()
    let docPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]

    var rootPath: String {
        return "\(docPath)/IconCreator/"
    }

    var backgroundColor = UIColor.grayColor()
    var textColor = UIColor.whiteColor()
    var lengths: [CGFloat] = [
        // Phone
        120.0,
        180.0,
        // Store
        1024.0,
    ]
    var fontScale: CGFloat = 0.75
    var fontName = ".SFUIDisplay-Ultralight"
    var string = "S"
```

## 初期化

ドキュメントディレクトリに画像保存用のフォルダを作成しています。
（既に存在した場合失敗するので`try?`を使っています）

```swift
    init() {
        try? fileManager.createDirectoryAtPath(
            rootPath,
            withIntermediateDirectories: false,
            attributes: nil
        )
    }
```

## 画像の作成

メインの処理です。

少し長いですが、

- コンテキストの作成・取得
- 背景色での塗りつぶし
- テキストの高さを取得して中央に描画
- UIImageの作成

をやっているだけです。

```swift
    private func create(length: CGFloat) -> UIImage {
        let size = CGSizeMake(length, length)
        let rect = CGRectMake(0.0, 0.0, length, length)

        let opaque = true
        let scale: CGFloat = 1.0
        UIGraphicsBeginImageContextWithOptions(size, opaque, scale)

        let context = UIGraphicsGetCurrentContext()

        CGContextSetFillColorWithColor(context, backgroundColor.CGColor)
        CGContextFillRect(context, rect)

        let attributes = textAttributes(length)
        let frame = string.boundingRectWithSize(
            size,
            options: [.UsesLineFragmentOrigin, .UsesFontLeading],
            attributes: attributes,
            context: nil
        )

        string.drawInRect(
            CGRectOffset(
                rect,
                0.0,
                CGRectGetMidY(rect) - CGRectGetMidY(frame)
            ),
            withAttributes: attributes
        )

        let image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return image
    }

    private func textAttributes(length: CGFloat) -> [String : AnyObject] {
        let fontSize: CGFloat = length * fontScale

        let defaultStyle = NSParagraphStyle.defaultParagraphStyle()
        let style = defaultStyle.mutableCopy() as! NSMutableParagraphStyle
        style.alignment = .Center
        let attributes = [
            NSFontAttributeName: UIFont(name: fontName, size: fontSize)!,
            NSForegroundColorAttributeName: textColor,
            NSParagraphStyleAttributeName: style,
        ]

        return attributes
    }
```

## 画像のプレビュー・保存

`preview`は作成した画像をそのまま配列で返し、`save`はその画像を`NSData`に変換して保存しています。

```swift
    func preview() -> [UIImage] {
        return lengths.map { create($0) }
    }

    func save() {
        for length in lengths {
            let image = create(length)

            let filename = "icon\(Int(length)).png"
            let data = UIImagePNGRepresentation(image)!

            fileManager.createFileAtPath(
                "\(rootPath)\(filename)",
                contents: data,
                attributes: nil
            )
        }
    }
```

全体的にForced Unwrapping(`!`)しまくってますが、このスクリプトは何かあったら落ちて欲しいので、そうしています。

# 動作確認

それでは、作成した`IconCreator`を実際に使ってみたいと思います。

### プレビュー

`preview`を呼んでみます。

```swift
let creator = IconCreator()
creator.preview()
```
右端にある`Quick Look`か`Show Result`アイコンをクリックすると、作成された画像を見ることができます。
一覧では縮小されちゃうので同じサイズに見えますが、それぞれの画像を開くと違うサイズになっていることがわかります。

![preview.png](https://qiita-image-store.s3.amazonaws.com/0/2191/0f5aaa86-5f71-02da-edfb-925e6f798683.png)

### 保存

いよいよ作成したアイコンを実際に書き出します。

```swift
creator.save()
print("$ open \(creator.rootPath)")
```
Debug areaを表示すると、コンソールにopenコマンドが出力されているはずなので、Terminalにペーストして実行します。

```shell-session
$ open /var/folders/yc/yh1b0bsx5bv3qfxhg1k3zyj00000gn/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.IconCreator-84F5EB55-FB19-4F28-8C1F-FF5981176470/Documents/IconCreator/
```

無事、保存されているようです。

![open.png](https://qiita-image-store.s3.amazonaws.com/0/2191/ce834d6c-6ed9-10a4-1b1a-24f619442a9a.png)

# カスタムフォント

Playgroundではカスタムフォントも使えます。
参考：[Custom fonts in Xcode playground - Stack Overflow](http://stackoverflow.com/questions/27472692/custom-fonts-in-xcode-playground)

例として、Google Fontsから[Pacifico](https://www.google.com/fonts/specimen/Pacifico)を借りてきます。
（見た目の変化がわかりやすいようにHandwritingなフォントを選びました。）

XcodeのProject navigatorを開いて、
`.ttf`ファイルを`Resources`にドラッグ＆ドロップします。

あとはIconCreatorに`loadFont`というメソッドを追加します。

```swift
    class func loadFont(name: String) {
        let font = NSBundle.mainBundle().URLForResource(name, withExtension: nil)!
        CTFontManagerRegisterFontsForURL(font, CTFontManagerScope.Process, nil)
    }
```

呼び出してみます。

```swift
let creator2 = IconCreator()
IconCreator.loadFont("Pacifico.ttf")
creator2.fontName = "Pacifico"
creator2.fontScale = 0.6
creator2.preview()
```

Sの文字が変わりました。（そのままだと切れてしまったのでフォントサイズも変えています。）

![pacifico.png](https://qiita-image-store.s3.amazonaws.com/0/2191/e9cbe7d2-6e31-f89a-637a-e0949c695c5a.png)


## アイコンフォント

カスタムフォントが使えるということは、アイコンフォントも使えます。
例えば、[Font Awesome](https://fortawesome.github.io/Font-Awesome/)のフォントを持ってきて同じようにやると…

```swift
let creator3 = IconCreator()
IconCreator.loadFont("FontAwesome.otf")
creator3.fontName = "FontAwesome"
creator3.string = "\u{f09b}"
creator3.preview()
```

GitHubなアイコンになりました。

![fontawesome.png](https://qiita-image-store.s3.amazonaws.com/0/2191/f0568a20-78d6-71f0-4144-03a93cb80d30.png)

# ハマったところ

```swift
UIFont.systemFontOfSize(UIFont.systemFontSize(), weight: UIFontWeightUltraLight).fontName
```

だと、`.SFUIText-Light`が、


```swift
UIFont.systemFontOfSize(100.0, weight: UIFontWeightUltraLight).fontName
```

とかだと、`.SFUIDisplay-Ultralight`が返ってきて困りました。

同じ`weight`を指定しても`fontSize`によって返ってくるフォント名が変わります。結局、デフォルトのフォントは決め打ちしました。

# 注意

- 実際にアプリに使う場合は、フォントのライセンスを要確認です。
- Pathの文字列を編集してる時にPlaygroundが実行されると、中途半端な名前のファイルができたりします。

# ソースコード

GitHubで公開しています。
<https://github.com/tnantoka/IconCreator>

# まとめ

いかがでしたでしょうか？
Playground、久々にちゃんと触りましたが初期の頃より安定してて良い感じでした。
Xcodeのアップデートでファイルアクセスに制限がかかったりしないことを祈ります。

それでは。