Swift4でFontを非同期ダウンロード


Swiftでシステムデフォルト以外のフォントを使いたい時が出て来ると思います。
物によっては.ttfなどをダウンロードしてプロジェクトファイルに入れるというのもありだと思います。

自分も使いたい状況が出て来たのですが、調べたところobj-cのものが多くSwiftでのやり方が少なかったので備忘録で残しておきます。

裏側がどうなってるのか詳しく理解していないので、FontどのFontが落とせるのか正確にわかっていません。(多分、Macにデフォルトで入ってたりFontBookで見れるの落とせるんじゃねーのか認識です)

Fontダウンロードついでに、GCD(Grand Central Dispatch)というのを使って非同期で処理を行わせています。

また、iPhoneに一度インストールしておくと二度目からは必要ないそうです。

```swift:fontDownload.swift
import Foundation
import UIKit

final class FontDownloadViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 非同期処理のグループ化変数
        let group = DispatchGroup()
        // 非同期処理(その１) medium font用
        let mediumQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".medium", attributes: .concurrent)
        // 非同期処理(その2) bold font用
        let boldQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".bold", attributes: .concurrent)
        
        mediumQueue.async(group: group) {
            // 游ゴシックのMediumを指定
            self.install(fontName: "YuGo-Medium")
        }
        boldQueue.async(group: group) {
            // 游ゴシックのBoldを指定
            self.install(fontName: "YuGo-Bold")
        }

        // 非同期処理 ２つが終わると呼ばれる
        group.notify(queue: DispatchQueue.main) {
            // 遷移処理など?
        }
    }
    
    func install(fontName: String) {
        // 終わったかどうか
        var result = false
        // スレッドの排他処理
        let semaphore = DispatchSemaphore(value: 0)
        // 終わるまで待つ
        while !result {
            self.coreInstall(name: fontName, completion: {
                result = $0
                // semaphoreのvalueをインクリメントする
                semaphore.signal()
            })
            // semaphoreのvalueが1以上の数であれば処理を終了、0か負の数であれば処理を待つ
            semaphore.wait()
            // 0.01秒処理を止める
            Thread.sleep(forTimeInterval: 0.01)
        }
    }
    
    func coreInstall(name: String, completion: @escaping (Bool) -> Void) {
        // UIFontのインスタンスが出来ればnilではない(iPhoneにインストールされている)
        if UIFont(name: name, size: 16.0) != nil {
            completion(true)
        }
        let desc = UIFontDescriptor(name: name, size: 16.0)
        CTFontDescriptorMatchFontDescriptorsWithProgressHandler( [desc] as CFArray, nil, { state, prog in
            print(state)
            switch state {
            case .didBegin:
                print("matching did begin: " + name)
            case .stalled:
                print("stalled")
            case .didMatch:
                print("match")
            case .willBeginQuerying:
                print("willBeginQuerying")
            case .willBeginDownloading:
                print("downloading will begin")
            case .downloading:
                let d = prog as NSDictionary
                let key = kCTFontDescriptorMatchingPercentage
                let cur = d[key]
                if let cur = cur as? NSNumber {
                    print("progress: %@%%", cur)
                }
                print("downloading")
            case .didFinishDownloading:
                print("downloading did finish")
            case .didFailWithError:
                print("downloading failed")
            case .didFinish:
                print("matching did finish")
                // 失敗しようがしまいが終わったらtrueを与えてメソッドをコール
                completion(true)
            default:break
            }
            return true
        })
    }
    
}

```

上記のはViewCotnrollerに書くパターンですが、UIFontを拡張して関数にしておくのもありだと思います。

ダウンロードが完了した次のViewからFontが適応されるかと思います。

何か間違いがあればすみません。教えていただけると助かります。