フォント
フォントを利用するには、
- MobileConfig を利用したプロファイル方式
- Assets.xcassets に埋め込むアセット方式
- Documents から読み込むドキュメント方式
- iOS13 以降対応したフォント方式
の四つがあります。
呼び方は適当なのでそこは気にしないでください。
各方式の比較
それぞれの大雑把な比較は以下のとおりです。
| プロファイル | アセット | ドキュメント | フォント | |
|---|---|---|---|---|
| 権利 | 難 | 難 | 易 | 難/易 |
| 利用 | - | - | - | iOS13 以降 |
| 拡張子 | ttf, otf | ttf, woff, woff2 | ttf, woff, woff2 | ttf, woff, woff2 |
ただし、フォント方式に関しては今回は別のフォントアプリでフォントをインストールするようなことを考えていないので、URL からダウンロードしてきたフォントファイルをフォントとしてインストールして利用することを考えています。
よって、ドキュメント方式とフォント方式は今回の場合は二つで一つということになります。
権利
いちばん大事なのがここで、再配布を禁止していたり商用利用が不可だったりするフォントは多数あります。
プロファイルにしろ、アセットにしろ.mobileconfigかassets.xcassetsに組み込まなければいけないので、これらの方式ではすべてのフォントを自由に配布したりすることはできません。
iOS アプリにフォントを組み込む記事を検索すると日本語でも英語でもほとんどがassets.xcassetsを利用した方法を紹介していますが、この方法は使えないわけです。
とはいえ、それぞれどのような違いがあるのかを調べてみることにしました。
プロファイル方式は
ttfないしはotfしかサポートしていませんが、バンドル方式はwoffやwoff2が利用できました。これらは圧縮率が高くてオススメです
テスト用アプリ
どのフォントが利用できるかはUIFont.familyNamesを参照すればわかります。
ここに表示されないフォントはどうやってもアプリ内から利用できないので、これでフォントが利用可能になっているかどうかを判断できます。
ContentView
struct ContentView: View { var body: some View { NavigationView(content: { List(content: { NavigationLink(destination: { FontListPicker() }, label: { Text("Font Lists") }) }) }) }}UIFontPickerViewControllerはSwiftUIでそのまま利用できないので、UIViewControllerRepresentableを利用します。
struct FontListPicker: UIViewControllerRepresentable { func makeCoordinator() -> Coordinator { Coordinator(self) }
func makeUIViewController(context: Context) -> UIFontPickerViewController { let controller: UIFontPickerViewController = UIFontPickerViewController() controller.delegate = context.coordinator return controller }
func updateUIViewController(_ uiViewController: UIFontPickerViewController, context: Context) { }
class Coordinator: NSObject, UIFontPickerViewControllerDelegate { private let parent: FontListPicker
init(_ parent: FontListPicker) { self.parent = parent }
func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) { guard let descriptor = viewController.selectedFontDescriptor, let name: String = descriptor.fontAttributes[.family] as? String, let font: UIFont = UIFont(name: name, size: UIFont.systemFontSize) else { return } UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true) } }}これでFontListPickerにインストールしたフォントが表示されるようになれば利用可能、ということです。
Capabilities
フォント方式でインストールしたフォントを利用するにはCapabilitiesからFontsを有効化してUse Installed FontsとInstall Fontsにチェックを入れます。
Use Installed Fonts にチェックを入れるとバイナリにフォントが同梱されていないと Invalid Binary で AppStore Connect から弾かれるので注意
プロファイル方式
Apple Configurator を使ってフォントをインストールする方式です。
iOS13 以前ではこれが利用されていたようなので、どうなるのか検証してみます。
導入の方法を書いていると長いので、先駆者の方の記事を載せておきます。
iOS にフォントをいっぱいインストールしたいを読めばオリジナルフォントをインストールするのは難しくないと思います。
検証
で、この方式でインストールしたフォントは何故かシミュレータだと反映されませんでした。
何故なのかはわかりません。
しかし、実機であれば全くの設定不要でフォントが読み込めます。Info.plist を編集する必要もないですし、何も必要ないです。
.font(.custom(FAMILY_NAME, size: UIFont.systemFontSize))みたいなことを書けばそれで終わりです。
| シミュレータ | iPhone/iPad | |
|---|---|---|
| Use Installed Fonts | 不要 | 不要 |
| フォントの読み込み | 不可 | 可 |
| 設定画面表示 | なし | なし |
| Xcode 設定 | 不要 | 不要 |
つまり、上のような結果になります。
構成プロファイルを作成するのが手間なこと以外はものすごく簡単だと思います。
必要なもの
さて、今回のケースだと構成プロファイルを作成するのに何が必要なのでしょうか。
- Apple Configurator 2
- FontForge
- スプラトゥーン用のフォント
このうちなんと Apple Configurator 2 は Windows 版が提供されていない。つまり、この時点で Windows ユーザーは構成プロファイルを利用したフォントのインストールができないことになります。
とはいえ、構成プロファイルは単純にフォントの Base64 バイナリが XML に貼り付けられているだけなので適当に TS でコードを書けば構成プロファイルを作成するウェブサイトを作成するのは難しくないと思います。それを転送するのがまた手間ですけれど…
マージするフォントは以下の URL から手に入ります。Web で普通に公開されているのでとても楽です。2 用と 3 用がありますが、韓国語と中国語を利用しないのであれば 2 用のフォントで十分です。
https://app.splatoon2.nintendo.net/fonts/bundled/ab3ec448c2439eaed33fcf7f31b70b33.woff2https://app.splatoon2.nintendo.net/fonts/bundled/0e12b13c359d4803021dc4e17cecc311.woff2https://app.splatoon2.nintendo.net/fonts/bundled/da3c7139972a0e4e47dd8de4cacea984.woff2https://app.splatoon2.nintendo.net/fonts/bundled/eb82d017016045bf998cade4dac1ec22.woff2四つのファイルはそれぞれスプラ 1 用の日本語と英語、スプラ 2 用の日本語と英語のフォントになっています。
日本語フォントには英語のフォントが全く含まれていないので、これらをマージしてしまえば良いことになります。
で、マージするには FontForge を利用するのが一番楽です。 手順は長いので割愛します。
マージしたフォントを ttf として出力し、構成プロファイルに組み込んでインストールすればフォントを利用することができます。
アセット方式
何も考えないなら一番楽なのがこれです。
ただし、アプリ自体にフォントをバンドルしなければいけない性質上、権利関係をクリアするのはほぼ不可能です。
配布不可なフォントを利用したい場合にはこの方式は使えません。
| シミュレータ | iPhone/iPad | |
|---|---|---|
| Use Installed Fonts | 不要 | 不要 |
| フォントの読み込み | 可 | 可 |
| 設定画面表示 | なし | なし |
| Xcode 設定 | 必要 | 必要 |
アセット方式でのカスタムフォント利用方法についてはいろいろ記事があるのですが、パッと目についたカスタムフォントを使用する方法をご紹介しておきます。
多分 typo だと思うのでタイトルを修正しておきました
Google Fonts などを利用するのであればこの方法で良いかもしれません。
ドキュメント方式
ドキュメント方式はアプリが持つDocuments以下にフォントのファイルをダウンロードして、そのフォントを読み込むタイプの対応方法です。
ユーザーが勝手にフォントをダウンロードしてくるのでアプリ自体にフォントをバンドルする必要はありません。
| シミュレータ | iPhone/iPad | |
|---|---|---|
| Use Installed Fonts | 不要 | 不要 |
| フォントの読み込み | 可 | 可 |
| 設定画面表示 | あり | あり |
| Xcode 設定 | あり | あり |
アプリ自身がインストールしたフォントを利用する場合は
Use Installed Fontsは不要だが、Install Fontsは必要でプロセスにインストールするだけであればInstall Fontsは不要
| Install Fonts Yes | Install Fonts No | |
|---|---|---|
| Use Installed Fonts Yes | 全て利用可 | 他のアプリでインストールしたフォントが利用可 |
| Use Installed Fonts No | アプリがインストールしたフォントは利用可 | .process のみ利用可 |
なにやらややこしいのですが、とりあえずどちらもチェックを入れて損はないです。ただし、Install Fonts を Yes にするのであればバイナリに必ずフォントを同梱してください。
この方式ができればめんどくさい構成プロファイルの作成が省略できて楽なのですが、この方式を導入するに当たって難しい点を挙げると、
- Core Text Functionsに関するドキュメントが少ない
- 起動時にフォントを読み込んで登録する必要がある
- 他の二つの方式ではアプリ自体及び構成プロファイルがフォントを自動で登録してくれていましたが、本方式ではアプリ起動時に登録する必要があります
- 多分ですが
AppDelegateで登録しておけばよいです
- 同一の FamilyName を持つフォントに対する読み込み方法がわからない
- 同一の FamilyName を持つフォントを登録しようとすると
CTFontManagerError.duplicatedNameで普通に怒られます
- 同一の FamilyName を持つフォントを登録しようとすると
- インストールダイアログがでない
- 本当に謎で、端末リセット直後の一回だけでたけどそれ以後音沙汰がないです
- 複数同時にインストールしようとするとそうなるのかもしれない
- FamilyName が異なるフォントがある
- 後述します
と、ハードルがものすごく高いのですがこの方式ができれば一番楽です。なぜならフォントの URL もそのフォントが持つ FamilyName の情報も全てわかっているからです。
FamilyName 問題
かなり大きな問題で、スプラトゥーン 1 用の漢字フォントはROWDayStdという FamilyName が設定されているにも関わらず、ひらがなと英語のフォントはSplatoon1とうい FamilyName になっているからです。
つまり、単純にフォントが持っている FamilyName で登録してしまうと漢字とひらがなが混在しているテキストに.font()を当てると漢字かひらがなのどちらか一方は普通のフォントで表示されてしまうという問題が発生します。これを解決するにはフォントを読み込んだときに FamilyName を変更して登録する必要があるのですが、それをやるとCTFontManagerError.duplicatedNameで怒られます。
SwiftUI でのフォントの読み込み方法を変えるかCTFontManagerあたりを上手いことやる必要があると思うのですがいかんせんドキュメントが少なすぎて手探り感が半端ないです。
ダイアログ問題
何故か端末をリセットした最初の一回だけでます。
.processでインストールした場合にはでてこないので、.persistentを指定する必要があると思います。
まだ調査不足です。
フォント関連のメソッド
インストール
CTFontManagerRegisterFontsForURL(CFURL, CTFontManagerScope, UnsafeMutablePointer<Unmanaged<CFError>?>?) -> Bool- 指定された URL のフォントを指定されたパラメータで登録する
CTFontManagerScope=.process以外は登録に失敗する
CTFontManagerRegisterFontDescriptors(CFArray, CTFontManagerScope, Bool, ((CFArray, Bool) -> Bool)?)- 指定されたフォント一覧を指定されたパラメータで登録する
CTFontManagerScope=.persistent以外は登録に失敗する
CTFontManagerRegisterFontsWithAssetNames(CFArray, CFBungle, CTFontManagerScope, Bool)- 指定されたファミリーネーム一覧を指定されたパラメータで登録する
CTFontManagerScope=.persistent以外は登録に失敗する
アンインストール
CTFontManagerUnregisterFontDescriptors(CFArray, CTFontManagerScope, ((CFArray, Bool) -> Bool)?)- 指定されたファミリーネーム一覧を指定されたパラメータで解除する
取得
CTFontManagerCopyRegisteredFontDescriptors(CTFontManagerScope, Bool) -> CFArray- 指定されたパラメータで登録されているすべてのフォントを取得して返す
- フォント一覧取得
CTFontDescriptorCopyAttribute(CTFontDescriptor, CFString) -> CFTypeRef?- 指定されたフォントの指定されたパラメータを返す
- フォントのパラメータ取得
その他
CTFontManagerSetAutoActivationSetting(CFString?, CTFontManagerAutoActivationSetting)- 指定されたバンドル ID のフォントを自動でアクティベーションする
パッと見ただけだと何がなんだかわからないと思うので解説します。
まず、フォントのインストール・アンインストールに関して二つのパラメータがあります。
それがscope: CTFontManagerScopeとenabled: Boolですが、これらは何も考えずにそれぞれ.persistentとtrueを指定するようにしましょう。特にenabled=falseを指定するとえらくめんどくさいです。
CTFontManagerScope
フォントの影響力を表す。
- none
- スコープなし
- process
- 現在のプロセスで unregistered が呼ばれるまで有効
- アプリ終了などでプロセスがキルされるとアンインストールされる
- 設定のフォントからインストールしたフォントが見れない
- プロセスキルでアンインストールされる以外は構成プロファイルと似ている
- persistent
- ユーザーのすべてのプロセスで unregistered が呼ばれるまで有効
- アプリを終了してもインストール状態が継続
- 設定のフォントからインストールしたフォントが見れる
- session
- macOS のみ有効なので今回は考慮しない
- user
- persistent と同じ
Enabled
登録されているフォントのうち有効なものを返すか無効なものを返すかを表す。
enabled=falseでフォントをインストールすると、設定画面からフォントが表示されず、かといって再度インストールしようとすると1 files have already been registered in the specified scope.のエラーが返ってくる。
falseを設定する意味が今のところ見えないので、通常はtrueで良い。
CTFontDescriptor
登録されているフォントの情報は以下の四つ
- CTFontRegistrationUserInfoAttribute
- NSCTFontFileURLAttribute
- NSFontFamilyAttribute
- NSFontNameAttribute
これを FontForge で確認できるフォント情報を見比べてみる。
| Splatoon 1 | Splatoon 2 | |
|---|---|---|
| Fontname | RowdyStd-EB-Kanji | KurokaneStd-EB-Kanji |
| Family Name | FowdyStd | KurokaneStd |
| Name For Humans | RowdyStd-EB-Kanji | KurokaneStd-EB-Kanji |
| NSFontFamilyAttribute | FOT-Rowdy Std EB | FOT-Kurokane Std EB |
| NSFontNameAttribute | RowdyStd-EB | KurokaneStd-EB |
| CTFontRegistrationUserInfoAttribute | ab3ec448c2439eaed33fcf7f31b70b33 | da3c7139972a0e4e47dd8de4cacea984 |
| NSCTFontFileURLAttribute | URL | URL |
すると何故か全然一致しないという謎の状態が発生した。
CTFontRegistrationUserInfoAttributeはファイル名なので、これだけを信用したほうが良い気がする。
もしくは、予め Fontname か Family Name がわかっているものでないと利用するのは難しいと思われる。
/// CTFontRegistrationUserInfoAttributeCTFontDescriptorCopyAttribute(descriptor, kCTFontRegistrationUserInfoAttribute) as? String/// NSCTFontFileURLAttributeCTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute) as? String/// NSFontNameAttributeCTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute) as? String/// NSFontFamilyAttributeCTFontDescriptorCopyAttribute(descriptor, kCTFontAttributeName) as? String多分上のようなコードでCTFontDescriptorからデータが取ってこれるが、最悪辞書なのでゴリ押しでもとれます。
メソッド詳細
CTFontDescriptorCopyAttribute
CTFontDescriptorから安全にプロパティを取得するメソッド。使い方は先程解説した通り。
func CTFontDescriptorCopyAttribute( _ descriptor: CTFontDescriptor, _ attribute: CFString) -> CFTypeRef?CTFontManagerRegisterFontDescriptors
[CTFontDescriptor]を一括でインストールするメソッド。とはいえCTFontDescriptorになっている時点で普通はインストールが完了しているはずなので、ここはまだ使い方がしっかり理解できていないと思われる。
今回のサンプルプログラムでは利用しなかった。
func CTFontManagerRegisterFontDescriptors( _ fontDescriptors: CFArray, _ scope: CTFontManagerScope, _ enabled: Bool, _ registrationHandler: ((CFArray, Bool) -> Bool)?)CTFontManagerRegisterFontURLs
func CTFontManagerRegisterFontURLs( _ fontURLs: CFArray, _ scope: CTFontManagerScope, _ enabled: Bool, _ registrationHandler: ((CFArray, Bool) -> Bool)?)フォントの URL を指定して一括でインストールするメソッド。便利なのに非推奨。
.persistentと.processのどちらでも使えると思われるが、.persistentにしたいなら以下のCTFontManagerRegisterFontsWithAssetNamesを利用するのが無難。
CTFontManagerRegisterFontsWithAssetNames
Assets.xcassetsに登録されているフォントをインストールする。
func CTFontManagerRegisterFontsWithAssetNames( _ fontAssetNames: CFArray, _ bundle: CFBundle?, _ scope: CTFontManagerScope, _ enabled: Bool, _ registrationHandler: ((CFArray, Bool) -> Bool)?)ちょっとわかりにくいので少し解説。
- fontAssetNames
- インストールしたいフォントのファイル名(拡張子不要)
- bundle
- 何も考えずに
CFBundleGetMainBundle()を指定すれば良い。
- 何も考えずに
CFBundle は NSBundle とは互換性がないようなので
Bundle.mainなどは利用できない
Xcode はビルド時にアセットの階層構造が全てなくなるのでバンドルされているファイルを取得して指定されたファイル名のフォントを取ってきているようだ。
端末リセット直後の初回インストール時のみダイアログが出現する。
CTFontManagerUnregisterFontDescriptors
フォントマネージャを使ってフォントをアンインストールするメソッド。
func CTFontManagerUnregisterFontDescriptors( _ fontDescriptors: CFArray, _ scope: CTFontManagerScope, _ registrationHandler: ((CFArray, Bool) -> Bool)?)ここでアンインストールすべきフォントを正しくとってこないと、全てのフォントが消えます。
guard let fontDescriptors: [CTFontDescriptor] = CTFontManagerCopyRegisteredFontDescriptors(.persistent, true) as? [CTFontDescriptor]else { return}のようなコードで登録されているフォントを全て取得してからフィルターをかけてアンインストールすべきフォントを正しく取得しましょう。
なお、インストールされていないフォントをアンインストールしようとしても特にエラーはでません。
CTFontManagerUnregisterFontURLs
func CTFontManagerUnregisterFontURLs( _ fontURLs: CFArray, _ scope: CTFontManagerScope, _ registrationHandler: ((CFArray, Bool) -> Bool)?)指定された URL のフォントを一括でアンインストールするメソッド。
インストールされてないフォントをアンインストールしようとするとエラーが返る。
CTFontManagerRegisterFontsForURL
指定された URL のフォントをインストールするメソッド。
func CTFontManagerRegisterFontsForURL( _ fontURL: CFURL, _ scope: CTFontManagerScope, _ error: UnsafeMutablePointer<Unmanaged<CFError>?>?) -> BoolURL を指定できるということはもちろんDocumentsからフォントをインストールすることもできますが.process以外が効きません。
実行した場合のエラーコードも載せておきます。
.noneSomeone attempted to (un)register one or more fonts with CTFontManager using scope kCTFontManagerScopeNone. That's not a valid scope for (un)registration, so we'll use kCTFontManagerProcess instead. This message will not be logged again.
.persistentkCTFontManagerScopePersistent is not supported by this function. Use API with registrationHandler block parameter.
ちなみにプロセス実行中しか効かないので、アプリを終了すればフォントは自動的にunregisteredされます。
よって、起動時に毎回インストールを実行する必要があります。やるならAppDelegateで実行するのが良いかと思われる。
CTFontManagerSetAutoActivationSettingを使えば自動でインストールするようにできるかもしれないけれど、まだ未調査です。
CTFontManagerCopyRegisteredFontDescriptors
指定されたスコープでインストールされているフォントを取ってきます。
func CTFontManagerCopyRegisteredFontDescriptors( _ scope: CTFontManagerScope, _ enabled: Bool) -> CFArray返り値はCFArrayとなっていますが、実質的に[CTFontDescriptor]と同じです。
補足説明
先人の記事に拠ればResource Tagにも追加すると書いてあるが、これは結局ファイルの存在チェックにしか使えず、バンドルしているならフォントがあるのは当たり前の話であるし、バンドルしていないならそもそも Resource Tag の値は設定できないので事実上やってもやらなくても良い設定になっています。
現状、書かなくてもフォントはインストールできるので特にこの手順は不要かと思います。
既存の問題を解消するために
さて、一番の理想としてはフォントはどこかのサーバーからダウンロードしてきてそれを永続インストールしたいわけです。
で、永続インストールするためには.processしか使えないCTFontManagerRegisterFontsForURLではなく.persistentが利用できるCTFontManagerRegisterFontsWithAssetNamesの方が便利です。
CTFontManagerRegisterFontsWithAssetNamesでDocumentsからインストールするCTFontManagerRegisterFontURLsでDocumentsからインストールするCTFontManagerRegisterFontsForURLを起動時に実行する
ということで候補に上がるのはこの三つ。
最初は 2 で終わりじゃないかと思っていたのですが、実行してみると以下のような306 エラーが出ました。
Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=306The file is not in an allowed location. It must be either in the application's bundle or an on-demand resource.つまり、指定された URL が良くなくて、バンドルかオンデマンドリソースにあるフォントを指定しろとあります。まあ確かに外部のへんてこなフォントをインストールできては困るので、これは仕方ないかもしれません。
で、バンドルに含めるのは再三ダメだといってきたので残るはオンデマンドリソースになります。
なんだこれとなったのですが、調べてみるとソシャゲとかでよくある「アプリインストール時には要らないけれど起動時にダウンロードされる追加コンテンツ」であることがわかりました。
じゃあこれで解決かと思ったのですが、オンデマンドリソースは Apple のサーバーからか自分のサーバーからしかインストールすることができません。Apple のサーバーにファイルを置いておくのはバンドルしているのと変わりませんし、自分のサーバーであってもそれは同じことです。
結局のところ「アプリが無条件に信頼しているところからしか.persistentとしてフォントはインストールできないよ」ということになります。
したがって 1, 2 の方式は無理だということがわかり、必然的に 3 の方式ということになります。
CTFontManagerSetAutoActivationSettingで自動登録はできないのか
無理です。
macOS 10.6+以降しか対応してませんでした。よって iOS では不可能です。
登録されたフォント情報を取得する
CTFontManagerCopyAvailablePostScriptNames()とCTFontManagerCopyAvailableFontFamilyNames()でインストールされているフォントがとってこれるので取ってきます。
CTFontManagerCopyRegisteredFontDescriptorsでとってこれるんじゃないのと思ったのですが、取ってこれませんでした。
どうも設定のフォントのところに登録されているフォントしかとってこれないっぽい
.processでインストールした場合はあそこに表示されないので仕方ないかなという気もします。
/// PostScriptNames[KurokaneStd-EB, RowdyStd-EB, Splatoon1, Splatoon2]/// FamilyNames[FOT-Kurokane Std EB, FOT-Rowdy Std EB, Splatoon1, Splatoon2]で、取得した結果が上のような感じでした。この値はこれから使うことになるので覚えておきます。
スコープと利用可能なメソッド
どれが使えてどれが使えないかがわかりにくいのでまとめました。
| メソッド | .persistent | .process |
|---|---|---|
| CTFontManagerRegisterFontsForURL | - | Documents |
| CTFontManagerRegisterFontURLs | - | Documents |
| CTFontManagerRegisterFontsWithAssetNames | Assets.xcassets | - |
| CTFontManagerUnregisterFontsForURL | - | Documents |
| CTFontManagerUnregisterFontURLs | - | Documents |
| CTFontManagerUnregisterFontDescriptors | Assets.xcassets | - |
| CTFontManagerCopyRegisteredFontDescriptors | OK | NG |
オンデマンドリソースを使わないのであれば.persistentはバンドルされた署名済みのフォントにしか使えません。Documentsなどのファイルを指定するとCTFontManagerErrorDomain Code=306が発生します。
また、その逆でバンドルされたフォントを.processで登録することもできません。登録しようとするとInvalid argumentが返ります。
バンドルされたフォント
バンドルしているならフォントの情報は全てわかっているはずなので何も考えずにインストールではCTFontManagerRegisterFontsWithAssetNamesを使っておいて、アンインストールするときにはCTFontManagerUnregisterFontDescriptorsとCTFontManagerCopyRegisteredFontDescriptorsを組み合わせて利用すると良いでしょう。
CTFontManagerCopyRegisteredFontDescriptorsは.persistentでインストールされたフォントしか取ってこれないので.processでインストールしたフォントはこの方法ではアンインストールできません
取得したフォント
外部から取得したフォントは署名がないのでシステムにインストールすることはできません。
| メソッド | .persistent | .process |
|---|---|---|
| CTFontManagerRegisterFontsForURL | - | Documents |
| CTFontManagerRegisterFontURLs | - | Documents |
| CTFontManagerUnregisterFontsForURL | - | Documents |
| CTFontManagerUnregisterFontURLs | - | Documents |
よって、インストールとアンインストールは上の四つのメソッドを使うことになります。何も考えずにFileManager.defaultとかで URL を取得して[CFURL]を経由してCFArrayに渡すだけ、難しいところもないです。
CTFontManagerRegisterFontURLsはCTFontManagerRegisterFontsForURLの完全上位互換です。
フォントのマージ
そしていちばん大事なところがここ、フォントのマージができるのかどうか。
ここまでいろいろ書いてきましたが、結局それらは理解を深めるために書いてきただけで、ここのマージができないと何の解決にもなりません。
調べたところ、二つの FontDescriptors をマージするようなメソッドはありませんでした。
と思っていたところ、大変有益な記事を見つけてしまった…
なんとカスケードフォントというものを利用することで「英語のときはこのフォント A、日本語のときはフォント B で表示したい」という要望を叶えることができると書いてある。
え、これ勝ったのでは???
UIFontDescriptor
現段階ではまだCTFontDescriptorという型なのでこれを変換します。ただし、継承クラスなので無条件に成功します。
UIFontDescriptorはそのままUIFontに突っ込めて、UIFontはそのまま SwiftUI のFontに突っ込めるので逆順に追うと以下のような流れになるわけです。
11. URL -> as10. CFURL -> CTFontManagerCreateFontDescriptorsFromURL()9. CFArray? -> unwrap8. CFArray -> as?7. [CTFontDescriptor]? -> unwrap6. [CTFontDescriptor] -> first5. CTFontDescriptor? -> unwrap4. CTFontDescriptor -> as3. UIFontDescriptor -> init2. UIFont (UIKit) -> init1. Font (SwiftUI)ということでアンラップも含めれば 11 段階遡ることで URL から SwiftUI で利用できる Font まで繋げられることがわかりました。
このときUIFontDescriptorに対して適切にカスケードフォントを設定することで一つのフォントファミリーで Splatfont1 と Splatfont2 に対応できるはずです。
では、そのコードを書いていきましょう。
SwiftUI での実装
上の例ではスプラ 2 向けのフォントを利用していましたが、スプラ 3 では中国語と韓国語に対応しなければいけないので最初か r あらこちらで実装します。
フォント
Splatoon1-common.3b7ce8b3c19f74921f51.woff2Splatoon1-symbol-common.38ddb9a11cb1f225e092.woff2Splatoon1-cjk-common.62441e2d3263b7141ca0.woff2Splatoon1JP-hiragana-katakana.7650dccc9af86f19f094.woff2Splatoon1JP-level1.fafc97f04a568e26ba52.woff2Splatoon1JP-level2.225bb1db5962c9d61773.woff2Splatoon1KRko-level1.a94dd3748648749f4583.woff2Splatoon1KRko-level2.fcce77dce5655afed7d2.woff2Splatoon1CHzh-level1.6b6af277c3dc45a8cf10.woff2Splatoon1CHzh-level2.a24ca5d538d0b6a0d086.woff2Splatoon1TWzh-level1.e991c1b3c2084df56d18.woff2Splatoon1TWzh-level2.054b111fb7118a083ff7.woff2フォントは全部で 12 種類で共通のフォントが 3 つあります。
| 言語 | フォント | 合計 |
|---|---|---|
| Common | 4 | - |
| JP | 3 | 7 |
| KO | 2 | 6 |
| CH | 2 | 6 |
| TW | 2 | 6 |
共通のフォントはプレイヤー名につけられる文字なので「記号・シンボル・ひらがな・かたかな・英語」です。
これらが含まれるのが上から四つのフォントファイルなのでこれらは必ず含む必要があります。なので日本語でも韓国語でも中国語でもなければフォントファイルは四つマージするだけで良いのですが、どうせなら日本語を突っ込めばいいので日本語とそれ以外で分けてしまうのが良いです。
enum Splatfont1: String, CaseIterable { case Splatoon1Common = "3b7ce8b3c19f74921f51" case Splatoon1SymbolCommon = "38ddb9a11cb1f225e092" case Splatoon1CjkCommon = "62441e2d3263b7141ca0" case Splatoon1JPHiraganaKatakana = "7650dccc9af86f19f094" case Splatoon1JPLevel1 = "fafc97f04a568e26ba52" case Splatoon1JPLevel2 = "225bb1db5962c9d61773" case Splatoon1KRkoLevel1 = "a94dd3748648749f4583" case Splatoon1KRkoLevel2 = "fcce77dce5655afed7d2" case Splatoon1CHzhLevel1 = "6b6af277c3dc45a8cf10" case Splatoon1CHzhLevel2 = "a24ca5d538d0b6a0d086" case Splatoon1TWzhLevel1 = "e991c1b3c2084df56d18" case Splatoon1TWzhLevel2 = "054b111fb7118a083ff7"}フォントはハッシュで区別できるのでしてしまいましょう。
これに対し、フォントをマージしてUIFontDescriptorとして返すメソッドを定義します。
UIFontを返してしまうとフォントのサイズの変更ができなくなるので注意
extension Splatfont1 { /// SplatNet3のURL var baseURL: URL { URL(string: "https://api.lp1.av5ja.srv.nintendo.net/static/media") }
/// フォントのURL /// 必ず存在するので強制アンラップしても問題ない var fontURL: CFURL { FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! .appendingPathComponent("static/media", conformingTo: .url) .appendingPathComponent(rawValue, conformingTo: .url) .appendingPathExtension("woff2") as CFURL }
/// EnumからUIFontDescriptorを読み込む var fontDescriptor: UIFontDescriptor? { guard let array: CFArray = CTFontManagerCreateFontDescriptorsFromURL(self.fontURL), let fonts: [CTFontDescriptor] = array as? [CTFontDescriptor], let font: CTFontDescriptor = fonts.first else { return nil } return font as UIFontDescriptor }
static let splatfont1jpja: UIFontDescriptor { [
] }}