✒️

iOSの追加ダウンロードフォントに、遊教科書体が追加されているので実装

2024/06/14に公開

iOS18公開後に追記

iOS18ではOS自体にシステムフォントの追加ダウンロード機能が付きました。iOS18以降ではこれがおすすめです

https://zenn.dev/samekard_dev/articles/7e32ac73bbb760

注意

はじめに

デバイスにフォントが追加できるということ

iOSデバイスに追加できるフォントには

  • Appleが提供しているもの
  • Apple以外が提供しているもの

の2種類があります。この記事ではAppleが提供しているフォントを扱います。

Appleが提供しているフォントを入れる機能について

iOS6からユーザーがフォントを追加する機能が付きました。アプリユーザーがダウンロードできるようにするには、アプリ開発者がアプリに処理を組み込みます。

これらのフォントはiOSの一部であるが容量を抑えるためにデフォルトでは提供されないフォント、と考えることも出来ます。

以下の性質があります

  • ダウンロードしたフォントはどのアプリでも使えるが、使うためにはそのアプリで認識工程が必要
  • アプリを起動するたびに認識工程が必要
  • シミュレータに入れた追加ダウンロードフォントは、Macを再起動すると失う

遊教科書体とは

次の画像の背景に色が付いている部分のフォントです。

iOSの日本語フォントはデフォルトでゴシック体と明朝体があります。それらに比べて遊教科書体は、はねやはらいのニュアンスが表現されていて、デフォルトフォントに比べて自然なフォントです。子供が使用する教育アプリや、文字自体に強いUX感を持つ(表現これでいいのか?)アプリなどにおすすめなフォントです。

日本語は文字が多くてフォント選択肢が少ない(フォントを作成するのが大変なので少ない)中で、この機能を検討したいところであります。

追加できるフォントの一覧 iOS 14

追加ダウンロードできる日本語フォントの一覧です。游教科書体以外もあります。

  • ヒラギノ角ゴ Std、ヒラギノ角ゴ StdN、ヒラギノ角ゴシック
  • クレー
  • Osaka
  • 凸版文久ゴシック、凸版文久見出しゴシック、凸版文久見出し明朝、凸版文久明朝
  • 筑紫 A 丸ゴシック、筑紫 B 丸ゴシック
  • 游ゴシック体、遊教科書体、遊教科書体 横用、游明朝体、游明朝体 +36ポかな

ソースコード

本題に入ります。

ソースコードは恥ずかしながらGitHubに上げております。

https://github.com/samekard-dev/FontDownloader

フォント名の登録

登録の形式を説明します。

基本的にStringです。ファミリーと各フォント名の階層構成があるので、タプルや配列を用いて構成しています。

[(formerName: String, fonts: [(latterName: String, fullName: String)])]

遊教科書体のところを抜粋すると

[
    ("YuKyo", [
        ("Medium", "YuKyo-Medium"),
        ("Bold", "YuKyo-Bold"),
    ]),
    ("YuKyo_Yoko", [
        ("Medium", "YuKyo_Yoko-Medium"),
        ("Bold", "YuKyo_Yoko-Bold"),
    ]),
]

このようになります。

  • fullName は実際のフォント名
  • formerNameとlatterNameはフォント名の前半と後半、これをキーにして実際に表示する文字列を Localizable.strings から取得

Localizable.stringsは下のような感じです。

"YuKyo" = "遊教科書体";
"YuKyo_Yoko" = "遊教科書体 横用";

他の言語にしたい人

日本語以外の言語に対応したい人がいるかもしれません。他の言語に対応している追加フォントを探すコードもお付けしています。

ja の部分(上の方)を書き換えてください。

処理の中心

処理の中心になるのは次の関数です。

func CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
    _ descriptors: CFArray,
    _ mandatoryAttributes: CFSet?,
    _ progressBlock: @escaping CTFontDescriptorProgressHandler
) -> Bool

この関数に、

  • フォント名(descriptors)
  • クロージャ(progressBlock)

を渡します(私は真ん中の引数は未使用)。このクロージャは、確認&ダウンロードの各段階で呼ばれます。

フォントに関してやりたいことは大きく分けて次の2つ

  • フォントの有無を確認するだけ
  • フォントをダウンロードをする

どちらの場合でもこの関数を呼びます。分岐は「ダウンロードするかしないか分岐」という工程で行います。フォントの有無を確認するだけのときはクロージャがfalseを返すようにします。するとダウンロードは実行されず有無の確認のみで終了します。trueを返すとダウンロードに入ります。

クロージャが呼ばれるタイミングは、フォントの状態とダウンロード指定の有無によって異なり、次のようになります。

すべての場合において、フォントの確認までは共通です。それぞれについて解説します。

  • デバイスを買ったときから入っている場合は、インストール状態を確定して終了
  • デバイスを買ったときには入っていないが、後からダウンロードしてすでにある場合は、ダウンロード工程の終了が呼ばれてからインストール状態を確定し終了
  • デバイス内に無い場合でダウンロードする場合はフルコース
  • デバイス内に無い場合でダウンロードしない場合は、ダウンロードするかしないか分岐のタイミングでfalseを返します。その後はダウンロード工程の終了が呼ばれてから終了
  • 対応していないフォント名を投げた場合は、確認して終了します。

注意点はダウンロードの実行がなくてもダウンロード工程の終了でクロージャ呼ばれる点です。工程を表す enum の名前が .didFinishDownloading なのでダウンロード工程の終了と書いてますが「ダウンロード工程ではもうやることが無いので次に行きます」ぐらいのニュアンスのほうが理解しやすいかと思います。

フォントの状態を書き換える

さて、先ほどのクロージャが呼ばれたらその工程に応じてフォントの状態を書き換える必要があります。

フォントの状態は

enum FontState: Equatable {
    case undefined
    case preparing
    case undownloaded
    case downloading(Int)
    case downloaded
    case unresponsive
}

のようにしました。

  • undefined は何も情報がないに等しい状態(アプリ立ち上げ直後)
  • preparing はダウンロード準備中
  • undownloaded はダウンロードできるがダウンロード指令がまだないもの
  • downloading(Int) はダウンロード中。数字は進捗の%
  • downloaded はダウンロードしてもうある
  • unresponsive はフォントの確認を要求したら、何もせずに帰ってきたもの

です。

フォントの状態を書き換える処理は単純なものです。一例としてダウンロード中のものを上げます。

func downloading(fontName: String, progress: Int) {
    FontStateStore.write(state: .downloading(progress), in: fontName)
    delegate?.stateHasChanged()
}

例のクロージャからフォント名と進捗%が飛んでくるので、フォント状態を書き換えます。その後、ViewControllerにフォント状態が変わったことを教えます。

View

View関係について。ファイルが3つあります。

  • ViewController (Viewグループの管理者)
  • List (フォント一覧)
  • Preview (フォントを使用して文字を表示)

ViewController

Viewグループの管理者

  • フォント状態変化の通知を受ける
  • エラー発生の通知を受ける
  • フォント一覧のフォント選択/ダウンロードのイベントを受ける

List

UICollectionViewを使用しています。

  • フォント一覧を表示
  • 各フォントの状態を表示
  • ダウンロードボタンを表示

Preview

指令されたフォントでサンプル文を表示する

Discussion