Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
25
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

やりたいこと

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
25
Help us understand the problem. What are the problem?