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