45
28

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-21

やりたいこと

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

UINavigationBar.appearance().barTintColor = UIColor.blue

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

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

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

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

UIView Extension

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

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

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

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

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

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

スタイルの異なるフォントを指定する場合

注意しなければいけないのは

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

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

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

    @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 の初期化時に以下の処理が呼び出されることになります。

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

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

解決方法

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

    @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 の初期化時に呼び出される処理は以下のようになり、ボールドフォントとノーマルフォントの住み分けができます。

    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 でも行けると思います。言語環境に応じてフォントを使い分けるようなケースでも有効に使える気がします。

45
28
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
28