あり物のSF Symbolsからカスタムシンボルをつくる

```
この記事は
Xcode 11.2.1, iOS 13.2, Swift 5.1, SF Symbols 1.1
で書かれています
```

## SF Symbols

以前、[SF Symbolsを取り上げた記事を書きました](https://qiita.com/OkonomiyakiYuki/items/76414d79235307322c8b)。あれから数ヶ月、Symbols関連で検索すると結構上位に結果表示されたりする（ライバルが少ない）ので、気分をよくしてまた書きたいと思います。

## 内製App

「ヘイSiri、焼きそばひとつ」で焼きそばが注文できればいいな、と思ってAppをつくりはじめてはや`n`年。設計やバックエンドの変更を繰り返し、いまだ完成の目処すら立ちませんが、また転がり落ちるとわかっている大岩を山頂へと押し上げるが如く、こつこつと焼きそばの合間をみては開発をつづけています。

そんなわけで先日はショッピングAppには必須の「購入履歴」機能の実装に取り組んでいました。時すでに2019年も終わろうかという折、SF Symbolsにも当然アダプトするべく、「購入履歴」にあうシンボルを探していました。

## 購入履歴

`SF Symbols`はイニシャルリリースのベータから数度バージョンアップされ、現在は1.1が最新です。風の噂でリリースのたびにシンボルの種類が増えているということですが、残念ながら今回の目的にあうものはざっと見たところ見当たりません。

でも、だからといって一からつくるのは避けたい（その理由は過去のSF Symbolsへの取り組みでご覧ください）。

なにかいい手はないものか・・・

## カスタマイズ、という手

SF Symbolsはこの種のApple謹製リソースとしてはかなり珍しいと思うのですが、提供されたシンボルの＜カスタマイズ＞が積極的に推奨されています（※Appleが特に指定する一部シンボルを除く）。

ではお言葉に甘えて、あり物を使ってみてはどうでしょうか。

そこで今度は視点を「そのもののピックアップ」から「元ネタ探し」へと変えて、もう一度リストを開きます。まず買い物といえばカートかショッピングバッグ、あるいはお金がシンボルとして定番だろうと、その辺から当たりました。

「購入した（会計済み）」ことの履歴なのだからカートじゃないだろうということで、ここはバッグをえらびます。
￼
<img width="396" alt="bag.fill.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/226e61e3-1699-e68a-a3fd-6c54780f6b6b.png">


次に、履歴といえば時間の経過を表す・・・時計？ リストっぽいのとか帳簿的なものとかもありなのかしれませんが、まあここは時計で。
￼
<img width="380" alt="clockwise.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/8fd47f4a-6149-8266-dee0-4900918969cf.png">


この二つを組み合わせてはどうだろうと思い、ベクターを編集するソフトであるAffinity Designerで開くべく`svg`形式でエクスポートすべくもう一度`bag`に戻ったら、周辺になにかがチラチラと見えるような見えないような・・・

￼なんだろう？
😐 じーっと見る。
<img width="380" alt="customizing-sf-symbols.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/a2076538-b5ec-518d-84a0-7db66a27e811.png">
😳 見えた！!

`bag`についた`plus`サインと`clock.fill`は、ある点（2点）を除いてほとんど一緒。加工もすごく簡単そう。ということでカスタマイズすることにしました。使ったソフトは前述の通りAffinity Designer。まだうまく使えないし、不安な部分もありますが、作業そのものはごくシンプルなものなので、これでいきます。

## 実際の作業の様子

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">How to create a symbol you want by customizing icons in SF Symbols. <a href="https://t.co/BwvDCZO3G9">pic.twitter.com/BwvDCZO3G9</a></p>&mdash; お好み焼 ゆき (@OkonomiyakiYuki) <a href="https://twitter.com/OkonomiyakiYuki/status/1202224315891978240?ref_src=twsrc%5Etfw">December 4, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

簡単ですね。これをウェイトとスケール全27の組み合わせで行います。


<img width="500" alt="customizing-sf-symbols-04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/68657d8b-6f06-efc5-e48c-d625dd8d2187.png">
手間ではあります。たしかに。
時間がないという方には、Apple推奨の優先順位として、

- まずはMedium-Regular（青い線で挟まれたもの）から。これは必須。
- つぎにSmall-RegularとLarge-Regularを
- その次にMedium-Semibold・・・

といった順番で埋めていくといいそうです。



### カスタマイズのための基礎的知識

ここでいったい何をしているのかを、実際に`UIBezierPath`を使って描画した下図（仮に“レオパルドン”と呼びます）をつかって説明します。



<img width="380" alt="customizing-sf-symbols-05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/85b44be5-8a44-884e-c8ef-739d9beff38c.png">

`UIBezierPath`ではまず最初に描画をはじめたい場所までポイントを移動させます。

```swift:LeopardonViewController.swift
let path = UIBezierPath()		
let initialPoint = CGPoint(.zero)
path.move(to: initialPoint)
```

ここではまだ描画はされていません。ポイントが移動しただけです。
描画するには`addLine(to:)`します。

```swift:LeopardonViewController.swift
path.addLine(to: secondPoint)
path.addLine(to: thirdPoint)
```

### Point、Node、Vertex
ここで注目したいのは、`addLine(to:)`の引数が`CGPoint`であることです。線（line）はあくまでもポイントとポイントをつなぐもので、Sketch.appやAffinity Designerといったドロー系ソフトやBlenderなどの3Dモデリングソフトでは、ポイントを動かせばその間に引かれた線もそれにともなって移動します。

※ポイントはSketchではPoint、Affinity DesignerではNode、BlenderではVertexと呼ばれる描画の最小単位で、`x`や`y`、あるいは`z`といった座標の値をもっているのですが、Appによって呼び方がちがうこととドメインによっても意味がちがっていたりとかなりややこしいので注意が必要です。

もしある形から似た別の形をつくろうと思えば、いくつかポイントを消したり足したりしてつくることが可能なのです。

レオパルドンの真ん中の十字型には赤字で書かれた12のポイントがありますが、6、7、10、9の四つを消す（コメントアウトする）ことで、時計っぽい形にすることが簡単にできます（図では必要のない5、11も消しています）。

```swift:LeopardonViewController.swift
path.addLine(to: fifthPoint)
// path.addLine(to: sixthPoint)
// path.addLine(to: seventhPoint)
path.addLine(to: eighthPoint)
```

<img width="380" alt="customizing-sf-symbols-06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/16789e6a-1729-446d-180d-6de00507d1b2.png">

この作業を視覚的にしたのが上の動画、というわけです。

## 結果

<img width="600" alt="customizing-sf-symbols-09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32738/0c5d6b80-bafd-4e79-eee5-7e95621a7955.png">

イメージとフォントのバランスがおかしいような気がしないでもないですが、なににしろ誇らしいです。
参考にコードを

```swift
let symbolWeights: [UIImage.SymbolWeight] = [.ultraLight, .medium, .black]
let symbolScales: [UIImage.SymbolScale] = [.small, .medium, .large]
let fontWeights: [UIFont.Weight] = [.ultraLight, .medium, .black]
let fontStyles: [UIFont.TextStyle] = [.title3, .title2, .title1]

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .default, reuseIdentifier: "staticCell")
    let idx = indexPath.row
    let titleFont = UIFont.preferredFont(forTextStyle: fontStyles[idx])

    let config = UIImage.SymbolConfiguration(pointSize: titleFont.pointSize, weight: symbolWeights[idx], scale: symbolScales[idx])
    let image = UIImage(named: "purchase.history", in: nil, with: config)!
    cell.imageView?.image = image.withRenderingMode(.alwaysTemplate)

    cell.textLabel?.text = "Purchase Hisotry"
    cell.textLabel?.textColor = UIColor.link
    cell.textLabel?.font = UIFont.systemFont(ofSize: titleFont.pointSize, weight: fontWeights[idx])
    
    return cell
}
```

## 最後に

近年MLやAR絡みでもとても便利なツールが次々とリリースされていて、より高機能なAppの作成がより容易になっています。自分のような趣味グラマーにとっての「自分はプロのデベロッパーではないからこの程度いいだろう」の「この程度」のレベルがどんどん上がって来ているのを感じます。

これまでであれば、無くてもよかっただろうもの（例：ボタンのアイコン）が、すでに素材（SF Symbols）が有るから付けないといけなくなった。と思ったら、なんとイメージに合うものが無い！ でももう付けないわけにはいかないから結局自分で作らないといけない、という、ともするとありがた迷惑なことにもなりかねない状況だったりもします。

でもそこはそれ、より手間を省いて、よりよいAppをつくるためにアイデアと工夫をこらして、今後もひるむことなく内製を続けていきたいと思います。

ありがとうございました。