iOS で UIAppearance を使ってグローバルなフォント変更を行う

## やりたいこと ##
iOS では `UIAppearance` という仕組みを使って、UI に関するグローバルな設定を記述できます。例えば、ナビゲーションバーの背景色をアプリで統一したければ

```swift
UINavigationBar.appearance().barTintColor = UIColor.blue
```

とかすればできます。しかし、アプリ全体でカスタムフォントに統一したい、などという場合に、

```swift
UILabel.apperance().font = UIFont(name: "Gills Sans", size: 14)
```

などとすると、確かに `UILabel` のフォントは "Gills Sans" になるのですが、それと同時に全てのフォントサイズが `14` になってしまいます。一アプリ同一フォントサイズというのは、ほぼ全てのアプリにおいて現実的とは言えません。`UIFont` は文字サイズも含んだクラスで、そもそも文字サイズに関係なく typeface だけ指定するというコンセプトが `UIKit` にはありません。

でも、なんとかそういう感じに、ライン一発でアプリ共通のフォントを指定したいです。

## UIView Extension ##
どこで見つけたのか忘れたけど、`UIView` の extension プロパティで `@objc` 修飾子を適用すると、自動的に `appearance()` でも呼べるらしいです。

例えばこうしてやります。

```swift
    @objc var substituteFontName: String {
        get {
            return font.fontName
        }
        set {
            font = UIFont(name: newValue, size: font.pointSize)
        }
    }
```

で、`AppDelegate.application(_:didFinishLaunchingWithOptions:...)` で

```swift
        UILabel.appearance().substituteFontName = "Gills Sans"
```

とすると、アプリ中のすべての `UILabel` のフォントが `Gills Sans` になります :boom:

## スタイルの異なるフォントを指定する場合 ##
注意しなければいけないのは

```swift
    UILabel.appearance().substituteFontName = fontName
    UILabel.appearance().substituteBoldFontName = boldFontName
```

などと、bold/italic など、別スタイルで別フォントを設定したい場合。__このように記述すると、すべてのラベルがボールドフォントになってしまいます。__

`UIAppearance` の実装の実体は、各 `UILabel` が初期化された時に、`UIAppearance` に登録された setter を順番に呼び出すというだけっぽいです。なので、以下のような extension の実装をしていた場合、

```swift
    @objc var substituteFontName: String {
        get {
            return font.fontName
        }
        set {
            font = UIFont(name: newValue, size: font.pointSize) // すべての UILabel 初期化時に呼び出されるコード
        }
    }
    @objc var substituteBoldFontName: String {
        get {
            return font.fontName
        }
        set {
            font = UIFont(name: newValue, size: font.pointSize) // すべての UILabel 初期化時に呼び出されるコード
        }
    }
```

`UILabel` の初期化時に以下の処理が呼び出されることになります。

```swift
    font = UIFont(name: substituteFontName, size: font.pointSize)
    font = UIFont(name: substituteBoldFontName, size: font.pointSize)
```

したがって、最初のノーマルフォント設定は常に後続のボールドフォント設定で上書きされてしまいます。


## 解決方法 ##

これを解決するためには、元のフォントのスタイルに応じて、適切なフォントを割り振ってあげる必要があります。

```swift
    @objc var substituteFontName: String {
        get {
            return font.fontName
        }
        set {
            // 元フォントがボールドでない場合のみ、指定のカスタムフォントを設定する。
            if font.fontName.range(of: "Medium") == nil {
                font = UIFont(name: newValue, size: font.pointSize)
            }
        }
    }
    @objc var substituteBoldFontName: String {
        get {
            return font.fontName
        }
        set {
            // 元フォントがボールドである場合のみ、指定のカスタムフォントを設定する。
            if font.fontName.range(of: "Medium") != nil {
                font = UIFont(name: newValue, size: font.pointSize)
            }
        }
    }
```

これで `UILabel` の初期化時に呼び出される処理は以下のようになり、ボールドフォントとノーマルフォントの住み分けができます。

```swift
    if font.fontName.range(of: "Medium") == nil {
       font = UIFont(name: newValue, size: font.pointSize)
    }
    if font.fontName.range(of: "Medium") != nil {
       font = UIFont(name: newValue, size: font.pointSize)
    }
```

めでたしめでたし。試してないですが、他の `UIView` でも行けると思います。言語環境に応じてフォントを使い分けるようなケースでも有効に使える気がします。