目標
- フォント (Font Family) を一括で変換する。
- 指定済みの Size, Weight, Italic を維持する。
- Weight はフォントによって数が異なるため、指定済みに最も近い Weight を利用する。
- どの Font Family にも適用できるよう汎用性の高いコードにする。
完成型
解説はどうでもいいのでコードの全体像を見たいという方はどうぞ
クリックして開く
extension UIFont {
convenience init?(familyName: String, weight: UIFont.Weight, isItalic: Bool = false, size: CGFloat) {
let font = UIFont
.fontNames(forFamilyName: familyName)
.compactMap({ UIFont(name: $0, size: size) })
.filter({ $0.isItalic == isItalic })
.min(by: { abs($0.weight.rawValue - weight.rawValue) < abs($1.weight.rawValue) })
self.init(name: font?.fontName ?? "", size: size)
}
private var traits: [UIFontDescriptor.TraitKey: Any] {
return fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
}
var weight: UIFont.Weight {
guard let weight = traits[.weight] as? NSNumber else { return .regular }
return UIFont.Weight(rawValue: CGFloat(truncating: weight))
}
var isItalic: Bool {
return traits[.slant] as? NSNumber != 0
}
}
extension UILabel {
@objc var fontFamily: String {
set {
guard let font = UIFont(familyName: newValue, weight: self.font.weight, isItalic: self.font.isItalic, size: self.font.pointSize) else { return }
self.font = font
}
get {
return self.font.familyName
}
}
}
// 使用時
UILabel.appearance().fontFamily = "Avenir"
最初に
UILabel
などは、UIAppearance
を用いることで、特定のプロパティを一括して設定できます。
UILabel.apperance().font = UIFont(name: "Gills Sans", size: 14)
これに加え、Weight (文字の太さ) なども考慮した上でフォントファミリーを指定する方法が、下記で良く解説されています。
https://qiita.com/yfujiki/items/7de9421e63dfbfbcc7d4
これを参考にさせていただきつつ、
Italic や、多数の Weight などへの考慮もするため、下記で解説するようなコードで実装しました。
フォント周りの理解
コードの前に、フォント周りについて少し説明します。理解してる人は読み飛ばしてください。
Family Names と Font Name
例で2種類だしてみました。違いを理解しておいてください。
Family Name | Font Name |
---|---|
Helvetica Neue | HelveticaNeue-Thin HelveticaNeue-ThinItalic HelveticaNeue HelveticaNeue-Italic etc... |
Hiragino Sans | HiraginoSans-W3 HiraginoSans-W6 HiraginoSans-W7 |
ここで大事なのは
- 各 Family Name に全ての Weight / Italic があるわけではない
→ もともと指定していた Weight に最も近い Weight を取得すべき - Font Name の命名規則に統一性は無い 1
→ 名称 (Light, Thin, など) をもとに Weight を判定すべきでない
Family と Name の取得 / 確認方法
A. Mac に入っている Font Book.app でみる
「PostScript名」が Font Name、「ファミリー」が Font Family です。2
B. コードで確認する
Apple 公式記事 からコピペしました。
for family in UIFont.familyNames.sorted() {
let names = UIFont.fontNames(forFamilyName: family)
print("Family: \(family) Font names: \(names)")
}
UIFont のイニシャライザ
UIFont.init?(name fontName: String, size fontSize: CGFloat)
を利用します。3
つまり、Font Family ではなく Font Name を使用することになります。
解説
1. Weight と Italic を取得する
UIFontDescriptor
経由で取得します。
UIFontDescriptor.TraitKey.slant
で傾き具合を取得できるので、傾きが 0 でない場合に Italic とします。
extension UIFont {
private var traits: [UIFontDescriptor.TraitKey: Any] {
return fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
}
var weight: UIFont.Weight {
guard let weight = traits[.weight] as? NSNumber else { return .regular }
return UIFont.Weight(rawValue: CGFloat(truncating: weight))
}
var isItalic: Bool {
return traits[.slant] as? NSNumber != 0
}
}
2. Font Family + Weight + Italic + Size でイニシャライズ
これらを踏まえ、Font Family・Weight・Italic・Size を引数としたイニシャライザを用意します。
- Family Name をもとに Font Name を取得
- Font Name をもとに UIFont を取得 (ここの Size はなんでもいい)
- Italic か否かでフィルタリング
- 引数で指定した Weight に最も近い Weight のものを取得
- イニシャライズ 4
extension UIFont {
convenience init?(familyName: String, weight: UIFont.Weight, isItalic: Bool = false, size: CGFloat) {
let font = UIFont
.fontNames(forFamilyName: familyName) // 1
.compactMap({ UIFont(name: $0, size: size) }) // 2
.filter({ $0.isItalic == isItalic }) // 3
.min(by: { abs($0.weight.rawValue - weight.rawValue) < abs($1.weight.rawValue) }) // 4
self.init(name: font?.fontName ?? "", size: size) // 5
}
}
3. UILabel で Font Family を指定できるように
これで終わりです。
用意した Weight / Italic get-only property と initializer を使用し、Font Family をもとにフォントを変更できるようにします。
存在しない Faimly Name を指定すると無視されます。
@objc
をつけることで UIAppearance
を使用できます。
extension UILabel {
/// Set value that can be obtained from `UIFont.familyNames`.
/// Can use UIAppearance because of using `@objc`.
@objc var fontFamily: String {
set {
guard let font = UIFont(familyName: newValue, weight: self.font.weight, isItalic: self.font.isItalic, size: self.font.pointSize) else { return }
self.font = font
}
get {
return self.font.familyName
}
}
}
UILabel.appearance().fontFamily = "Avenir"
環境 / 参考
Swift | iOS |
---|---|
5 | 13 |
- iOS で UIAppearance を使ってグローバルなフォント変更を行う
https://qiita.com/yfujiki/items/7de9421e63dfbfbcc7d4 - Stack Overflow | Font Weight の取得
https://stackoverflow.com/a/53818276 - Apple Documentation | UIFontDescriptor.TraitKey.slant
https://developer.apple.com/documentation/uikit/uifontdescriptor/traitkey/1616686-slant - Apple Documentaion | Adding a Custom Font to Your App
https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app
-
大体のフォントは
<FamilyName>-<Weight><Italic>
のような感じですが、例で挙げたような Hiragino Sans パターンなどもありえます。 ↩ -
このアプリ内で表示されているフォントはPC内のものなので、開発中のアプリ内で全て使用できるわけではありません。逆に載っていないものも使用できるものがあります。 ↩
-
init(descriptor: UIFontDescriptor, size pointSize: CGFloat) というのもあります ↩
-
convenience イニシャライザを使用しているので
self.init
していますが、 class関数でも良ければ、そのままfont
変数を返すだけで良いですね。お好みでどうぞ。 ↩