24/7 twenty-four seven

iOS/OS X application programing topics.

iOS 6 or 7で「游ゴシック体」や「ヒラギノ丸ゴシック」を使う

20131027032814


OS XではMarvericks (10.9) から「游ゴシック体」と「游明朝体」が標準搭載されたことで話題になりましたが、実はiOSでも6以上からこれらのフォントが使用できます。


iOS 6から追加ダウンロードフォントという仕組みが導入され、游ゴシック体などのフォントはその追加ダウンロードフォントという形で提供されています。
アプリケーションからは必要に応じてフォントをダウンロードして利用します。

フォント一覧

iOS 6で使えるフォントはアップルの下記のページに記載されています。
このページの下の方に追加情報として記載されているフォントがダウンロードフォントになります。
iOS 6:フォントリスト (http://support.apple.com/kb/HT5484?viewlocale=ja_JP&locale=ja_JP)


上記のページに記載されている追加ダウンロードフォントのうち、日本語のフォントは以下の8つです。

  • Hiragino Kaku Gothic Std W8 (ヒラギノ角ゴ Std W8)
  • Hiragino Kaku Gothic StdN W8 (ヒラギノ角ゴ StdN W8)
  • Hiragino Maru Gothic Pro W4 (ヒラギノ丸ゴ Pro W4)
  • Hiragino Maru Gothic ProN W4 (ヒラギノ丸ゴ ProN W4)
  • YuGothic Bold (游ゴシック体 ボールド)
  • YuGothic Medium (游ゴシック体 ミディアム)
  • YuMincho Demibold (游明朝体 デミボールド)
  • YuMincho Medium (游明朝体 ミディアム)


このうちヒラギノに関してはProNとPro、StdNとStdの違いで2書体になってるので実質6書体ですね。
ヒラギノ丸ゴシックやヒラギノ角ゴシックのW8などは標準搭載のフォントとはかなり趣きが異なるので使いどころによって重宝します。


iOS 7ではさらに少しフォントが追加されているのですがそれについては別の記事で記載します。

APIについて

追加ダウンロードフォントを利用するには、必要に応じてダウンロードの処理をします。
ダウンロードには`CoreText.framework`の下記のAPIを利用します。

bool CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
    CFArrayRef                          descriptors,
    CFSetRef                            mandatoryAttributes,
    CTFontDescriptorProgressHandler     progressBlock) CT_AVAILABLE(10_9, 6_0);

このAPIはドキュメントに解説が載っていないので、利用方法はヘッダファイルを見ます。


また、アップルから公式のサンプルコードが提供されていますのでこちらも参考になります。
DownloadFont - iOS Developer Library (https://developer.apple.com/LIBRARY/IOS/samplecode/DownloadFont/Introduction/Intro.html)


情報は少ないですが、特に難しいことはなく、1番目の引数にFont Descriptorの形でフォント情報を渡すと、適宜ブロックのコールバックが呼ばれるという仕組みです。


追加ダウンロードフォントについては、フォント名がわかっているのでフォント名からFont Descriptorを作ります。

NSDictionary *attributes = @{(id)kCTFontNameAttribute: fontName};
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attributes);
NSArray *fontDescriptors = @[(__bridge id)fontDescriptor];
CFRelease(fontDescriptor);


そして、Font Descriptorを先ほどのAPIに渡せば、必要に応じてフォントをダウンロードし、アプリケーションで利用できるように登録するところまでやってくれます。
ダウンロードの進捗やトータルのサイズなどの情報はブロックの引数の`progressParameter`に入っています。
アプリケーションでダウンロード中のプログレスバーなどを表示するにはこのパラメータの値を利用します。


ダウンロードされたフォントはアプリケーションごとに保存されるわけではなく、すべてのアプリケーションで共有の場所に保存されます。
例えば下記の場所です。

file:///private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/83f5ce0efa7a810b73a7231c0e107f2955f2c85c.asset/AssetData/Yu%20Gothic%20Bold.otf


つまり、あるアプリケーションでフォントがダウンロードされていれば(例えばiBooksなど)、別のアプリケーションではダウンロード処理はスキップしてそのフォントを利用できます。
ただ、一度ダウンロードされたフォントでもデバイスの空き容量によって削除されることがあるので、プログラムはそれを考慮して作成する必要があります。
また、すでに他のアプリケーションでフォントがダウンロードされていても、自分のアプリケーションで利用可能にするには上記のAPIを呼ぶ必要があります。
さらに、どのアプリケーションでダウンロードしたかにかかわらず、一度アプリケーションを終了すると利用登録が解除されてしまうので、次に起動したときには再度上記のAPIを呼ぶ必要があります。


まとめると、

  • 追加ダウンロードフォントはデバイス全体で共有される。
  • ダウンロードされたフォントは、自動的にシステムから削除されることがある。
  • フォントがダウンロード済みならダウンロード処理は自動的にスキップされる。
  • フォントが他のアプリケーションによってダウンロード済みであっても、自分のアプリケーションで利用可能にするにはこのAPIを呼ぶ必要がある。
  • 自分のアプリケーションでダウンロードしたフォントであっても、アプリケーションを終了したら利用登録が解除されるので、再度利用可能な状態にするにはこのAPIを呼ぶ必要がある。


ややこしいように感じますが、ダウンロードのAPIは必要ならダウンロードされ、ダウンロード済みならダウンロードはスキップされてロード処理だけをする、という動きをするので、要するに最初に利用しようとしたときにダウンロードのAPIを呼べばいいということになります。
ただ、単純にダウンロードのAPIを毎回呼ぶとすると、ダウンロードしたくないときにもダウンロードされてしまうので、必要に応じてダウンロードはキャンセルできるようにしたほうがいいでしょう。

フォントのダウンロードおよび利用登録

追加ダウンロードフォントを利用可能にする処理は下記のようになります。
ダウンロードの処理はフォントがどのアプリケーションによってもダウンロードされてないときのみ行われます。
フォントがダウンロード済みであっても、アプリケーションを起動しただけの状態では利用可能になっていないので、この処理をする必要があります。その場合、コールバックはダウンロードのステータスになることはなく、短時間で`kCTFontDescriptorMatchingDidFinish`の状態になります。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    NSDictionary *parameter = (__bridge NSDictionary *)progressParameter;
    double progressValue = [parameter[(id)kCTFontDescriptorMatchingPercentage] doubleValue];
    
    if (state == kCTFontDescriptorMatchingDidBegin) { // 処理の開始に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            // ダウンロードはサブスレッドで行われるのでUIの更新などはメインスレッドで行う
            ...
        });
    } else if (state == kCTFontDescriptorMatchingDidFinish) { // 処理の終了時に1度だけ呼ばれる
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            // この時点でフォントが利用可能になる
            ...
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        // フォントが未ダウンロードの場合のみ、ダウンロードの開始前に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDownloading) {
        // ダウンロード中、ダウンロードの進捗によって適宜呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
        // ダウンロード完了時に呼ばれる
        ...
    } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
        // ダウンロードが失敗したときに呼ばれる
        ...
    }
    
    return (bool)YES;
});


ダウンロード済みのフォントがある場合にフォントのロードだけ行い、ダウンロードはしたくないという場合は、例えば下記のようにします。

CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)fontDescriptors, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
    if (state == kCTFontDescriptorMatchingDidFinish) {
        dispatch_async( dispatch_get_main_queue(), ^ {
            UIFont *font = [UIFont fontWithName:fontName size:1.0f];
            if (font) {
                if ([self.delegate respondsToSelector:@selector(fontDownloaderDidFinish:fontName:)]) {
                    [self.delegate fontDownloaderDidFinish:self fontName:fontName];
                }
            }
        });
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
        return (bool)NO;
    }
    
    return (bool)YES;
});


`kCTFontDescriptorMatchingWillBeginDownloading`で`NO`を返しているのでそれ以上の処理は行われません。
もしダウンロード中のキャンセルをサポートする場合にも同様にすることで実現できます。


アップルのサンプルコードは主に中国語フォントを使っているので、日本語フォントについて簡単に試せるようにしたコードをGithubで公開しているので、よかったらご覧ください。

kishikawakatsumi/DownloadFont · GitHub