【Swift】FontAwesome 5を使ったバッジを作ってみた

しずおかオンラインで「[まちぽスタンプ](https://machipo.jp/stamp)」というスタンプラリーができるiOSアプリを開発しているKazuomatzです。
[<img src="https://qiita-image-store.s3.amazonaws.com/0/39570/19e9b6b0-fabb-2886-a8cf-eb307aa5a808.png" width="120"/>](https://machipo.jp/stamp)
このアプリで、スタンプラリーのミッションを達成すると、iPhoneのアクティビティやPokémon GOのように報酬としてバッジが貰えると楽しいと思い、バッジの実装を行いました。
バッジには色々なアイコンを表示したいですよね。そのため、色と絵柄を自由に組み合わせで様々なバッジを作るためのライブラリを考えました。
アイコンをひとつひとつ作成するのは現実的ではないので、Web開発ではお馴染みの[FontAwesome]
(https://fontawesome.com)の豊富なアイコンを使って実現したいと思いました。

最終的にできあがったアプリの画面は、こんな感じ。
<img src="https://qiita-image-store.s3.amazonaws.com/0/39570/937e7810-a053-aec6-3651-c177cca685f1.png" width="40%"/>
何となくバッジ感があってよい感じです。

スタンプラリーを管理する管理コンソールでアイコンと色を選択してバッジを設定しておきます。そのスタンプラリーのミッションを達成するとバッジを取得できる仕掛けです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/39570/abab4118-46ca-5877-6927-40554fd4f6df.png" width="60%"/>

このような形で、カラーとFontAwesomeのフォント名を選択して設定します。アプリとはAPIでカラーコードとフォント名とでインタフェイスを取っています。

```son:api.json（例）
{
   "badge_title" : "ミッションクリア",
   "badge_color" : "#ed6d46",
   "badge_icon"  : "fa-spoon"
}
```

このアプリを開発した時点では、FontAwesome 4.7を使いましたが、時代はFontAwesome 5なので、FontAwesome5 対応にして、[github](https://github.com/kazuomatz/AwesomeBadge)で公開しましたので紹介します。

# AwesomeBadge
<img src="https://user-images.githubusercontent.com/2704723/51324154-4de49080-1aad-11e9-904c-4046d65d881b.png" width="50%"/>

サンプルアプリは[github](https://github.com/kazuomatz/AwesomeBadge)からダウンロードできます。

[CocoaPods](https://cocoapods.org/pods/AwesomeBadge)でも使えます。

```ruby:Podfile
pod 'AwesomeBadge'
```
内部でFontAwesomeのラッパーライブラリー[FontAwesome.swift](https://github.com/thii/FontAwesome.swift)を使用しています。

## 使い方

使い方はこんな感じです。

```swift:hoge.swift
import AwesomeBadge

let badgeView = AwesomeBadgeView(
            fontStyle: .brands,            // font awesome style .solid / .regular / .brands
            fontName: "fa-android",        // font awesome font name 'fa-xxxxx'
            frame: CGRect(x:0, y:0, width: 100,height: 100),
            backgroundColor: "#a4c639"     // Color (Hex String)
        )
self.view.addSubView(badgeView)       
```
<img src="https://user-images.githubusercontent.com/2704723/51325372-1fb48000-1ab0-11e9-9cc9-b282fa0dbff2.png" width="20%"/>

- FontAwesome 5 は、スタイルがsolid / regular / brandsから選択できますので、スタイルを選択できます。
- fontNameは文字列で指定します。FontAwesome.swiftでは、フォント名がすべてenumで定義されていますが、APIでのやりとりを考えるとStringの方が何かと便利なので、引数はフォント名をStringで指定するようにしました。同じ理由で、色もUIColorではなくStringとしています。

```swift:hoge.swift
let badgeView = AwesomeBadgeView(
            fontStyle: .brands,            // font awesome style .solid / .regular / .brands
            fontName: "fa-android",        // font awesome font name 'fa-xxxxx'
            frame: CGRect(x:0, y:0, width: 100,height: 100),
            backgroundColor: "#a4c639"     // Color (Hex String)
            gradient: false               // not add GradientLayer
        )
self.view.addSubView(badgeView)       
```

<img src="https://user-images.githubusercontent.com/2704723/51325479-5a1e1d00-1ab0-11e9-8143-a772138611c0.png" width="20%"/>

こちらはお好みですが、gradient = false にするとグラデーションのないフラットなバッジになります。

```swift:hoge.swift
@IBOutlet weak var badgeView:AwesomeBadgeView!

badgeView.drawBadge(
    fontStyle: .solid,
    fontName: "fa-star-and-crescent",
    radius: 200,
    backgroundColor: "#273751",
    gradient: true,
    foregroundColor: "#f4e242"
)
```
<img src="https://user-images.githubusercontent.com/2704723/51327322-6906ce80-1ab4-11e9-93a6-bbd8583f990f.png" width="20%"/>

Interface BuilderでAwesomeBadgeViewを埋め込む場合は、drawBadgeをコールします。
foregroundColorも指定できます（デフォルトは#ffffff)。

# 実装について

実際にBadgeを作成しているコードは以下の通りです。

```swift:AwesomeBadgeView.swift
public func drawBadge(fontStyle:FontStyle ,fontName:String, radius:CGFloat, backgroundColor:String, gradient:Bool = true, foregroundColor:String = "ffffff") {
        
        // 再呼び出しを考慮して、SubViewをすべて削除
        self.subviews.forEach { (view) in
            view.removeFromSuperview()
        }
        
        self.backgroundColor = UIColor(hex: backgroundColor)
        self.clipsToBounds = true
        self.backgroundColor = .clear
        self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: radius, height: radius)
        
        // バッジの縁
        let boderWidth:CGFloat = radius * 0.08
        let backgroundView:UIView = UIView(frame:self.frame)
        
        // バッジの縁を20%暗い色にする
        backgroundView.backgroundColor = UIColor(hex: backgroundColor).darker(by: 20)
        backgroundView.layer.cornerRadius = radius / 2
        
        // フォント画像を配置するView
        let badgeBackgroundViewFrame = CGRect(x: boderWidth, y: boderWidth,width: radius - boderWidth * 2, height: radius - boderWidth * 2)
        let badgeBackgroundView = UIView(frame: badgeBackgroundViewFrame)
        badgeBackgroundView.layer.cornerRadius = (radius - boderWidth * 2) / 2
        badgeBackgroundView.clipsToBounds = true
        badgeBackgroundView.backgroundColor = UIColor(hex: backgroundColor)
        
        // 指定があればグレデーションのLayerを追加します。
        if gradient {
            let color = UIColor(hex: backgroundColor)
            let gradientLayer:CAGradientLayer = CAGradientLayer(layer: badgeBackgroundView.layer)
            gradientLayer.frame = badgeBackgroundView.bounds
            gradientLayer.colors = [
                color.lighter(by:60)?.cgColor as Any,
                color.cgColor  as Any,
                color.darker(by:30)?.cgColor as Any
            ]
            gradientLayer.cornerRadius = (radius - boderWidth * 2) / 2
            badgeBackgroundView.layer.addSublayer(gradientLayer)
        }
        
        // スタイル、フォント名を指定して、フォントが描画されたUIImageViewを取得します。
        let iconSize = CGSize(width: radius - boderWidth * 4, height: radius - boderWidth * 4)
        let imageView:UIImageView = UIImageView(frame:CGRect(x:0,y:0,width:radius - boderWidth * 2, height:radius - boderWidth * 2))
        imageView.contentMode = .center
        
        let faFontName = FontAwesomeIcons[fontName]
        if  faFontName != nil {
            let image = UIImage.fontAwesomeIcon(name: FontAwesome(rawValue: faFontName!)!, style: FontAwesomeStyle(rawValue: fontStyle.rawValue)!, textColor: UIColor(hex:foregroundColor), size: iconSize)
            imageView.image = image
            imageView.backgroundColor = .clear
            imageView.layer.cornerRadius = (radius - boderWidth * 2) / 2
        }
        badgeBackgroundView.addSubview(imageView)
        backgroundView.addSubview(badgeBackgroundView)
        self.addSubview(backgroundView)
    }
```

やっていることを図解するとこんなイメージになります。
![AwesomeBadge.png](https://qiita-image-store.s3.amazonaws.com/0/39570/83dc44da-40fb-0e76-2f6d-2a81befde9ad.png)

手軽にバッジを生成できるので、アプリのアクセントとして使ってみて下さい。