iOSアプリでのフォントの扱い方
フォント
フォントを利用するには、
- 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.woff2
https://app.splatoon2.nintendo.net/fonts/bundled/0e12b13c359d4803021dc4e17cecc311.woff2
https://app.splatoon2.nintendo.net/fonts/bundled/da3c7139972a0e4e47dd8de4cacea984.woff2
https://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がわかっているものでないと利用するのは難しいと思われる。
/// CTFontRegistrationUserInfoAttribute
CTFontDescriptorCopyAttribute(descriptor, kCTFontRegistrationUserInfoAttribute) as? String
/// NSCTFontFileURLAttribute
CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute) as? String
/// NSFontNameAttribute
CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute) as? String
/// NSFontFamilyAttribute
CTFontDescriptorCopyAttribute(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>?>?
) -> Bool
URLを指定できるということはもちろんDocuments
からフォントをインストールすることもできますが.process
以外が効きません。
実行した場合のエラーコードも載せておきます。
.none
Someone 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.
.persistent
kCTFontManagerScopePersistent 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=306
The 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 -> as
10. CFURL -> CTFontManagerCreateFontDescriptorsFromURL()
9. CFArray? -> unwrap
8. CFArray -> as?
7. [CTFontDescriptor]? -> unwrap
6. [CTFontDescriptor] -> first
5. CTFontDescriptor? -> unwrap
4. CTFontDescriptor -> as
3. UIFontDescriptor -> init
2. UIFont (UIKit) -> init
1. Font (SwiftUI)
ということでアンラップも含めれば11段階遡ることでURLからSwiftUIで利用できるFontまで繋げられることがわかりました。
このときUIFontDescriptor
に対して適切にカスケードフォントを設定することで一つのフォントファミリーでSplatfont1とSplatfont2に対応できるはずです。
では、そのコードを書いていきましょう。
SwiftUIでの実装
上の例ではスプラ2向けのフォントを利用していましたが、スプラ3では中国語と韓国語に対応しなければいけないので最初かrあらこちらで実装します。
フォント
Splatoon1-common.3b7ce8b3c19f74921f51.woff2
Splatoon1-symbol-common.38ddb9a11cb1f225e092.woff2
Splatoon1-cjk-common.62441e2d3263b7141ca0.woff2
Splatoon1JP-hiragana-katakana.7650dccc9af86f19f094.woff2
Splatoon1JP-level1.fafc97f04a568e26ba52.woff2
Splatoon1JP-level2.225bb1db5962c9d61773.woff2
Splatoon1KRko-level1.a94dd3748648749f4583.woff2
Splatoon1KRko-level2.fcce77dce5655afed7d2.woff2
Splatoon1CHzh-level1.6b6af277c3dc45a8cf10.woff2
Splatoon1CHzh-level2.a24ca5d538d0b6a0d086.woff2
Splatoon1TWzh-level1.e991c1b3c2084df56d18.woff2
Splatoon1TWzh-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 {
[
]
}
}