やりたいこと
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 になります ![]()
スタイルの異なるフォントを指定する場合
注意しなければいけないのは
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 でも行けると思います。言語環境に応じてフォントを使い分けるようなケースでも有効に使える気がします。