目標
- フォント (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変数を返すだけで良いですね。お好みでどうぞ。 ↩