Help us understand the problem. What is going on with this article?

iOSのDynamic Typeについて

Dynamic Type (ダイナミックタイプ)

Dynamic TypeはiOS上でデフォルトの文字の大きさを変更する機能です。対応しているアプリの文字サイズを一度に全部変えられます。
大きさだけでなくフォントの種類やスタイルも決められているので、Apple推奨の見た目を簡単に得られるというメリットがあります。その一方で、考慮しなければならない条件がひどく増えてしまう、厄介な機能でもあります。テストの手間を増やすだけでなく、画面設計にも大きく影響を及ぼします。
本文章は、Dynamic Typeに対する備忘録です。
対象OS:iOS9/iOS10/iOS11
対象Xcode:9.4

Dynamic Typeを使うためのユーザー操作

設定アプリに2通り用意されています。

  • その1:(iOS9 - iOS13)
    • [設定] -> [画面表示と明るさ] -> [文字サイズを変更]
  • その2:(iOS9 - iOS12)
    • [設定] -> [一般] -> [アクセシビリティ] -> [さらに大きな文字]
  • その2:(iOS13)
    • [設定] -> [アクセシビリティ] -> [さらに大きな文字]

その2では「さらに大きな文字」のスイッチをオンにすることができ、文字サイズの選択肢を大幅に増やすことができます。

フォントの指定方法

インターフェースビルダーで指定

フォントを選択する際、Text Stylesにカテゴライズされているものを選びます。
XCodeのフォント選択の図

コードでのフォント指定

UIFontクラスのクラスメソッドpreferredFont()に"スタイル"を指定することで、設定に応じたフォントオブジェクトを得られます。

let font = UIFont.preferredFont(forTextStyle: UIFontTextStyle(rawValue: style))

動的な設定変更に追従するために

ダイナミックタイプは設定アプリからいつでもサイズが変えられます。
この変更に動的に追従するためには、もう一手間が必要です。

iOS10以降、UILabel、UITextField、UITextViewは自動的に更新される

インターフェースビルダーで、Dynamic Typeの[Automatically Adjust Font]をチェックすると、表示される文字が自動的に更新されるようになります。
xcodeSelectDynamicType_small.png

コードでは、UIContentSizeCategoryAdjustingプロトコルのadjustsFontForContentSizeCategorytrueにすることで同様に自動的に処理されるようになります。

hogeLabel.adjustsFontForContentSizeCategory = true

UIContentSizeCategoryAdjustingプロトコルに対応しているのは、UILabelUITextFieldUITextViewだけです。
この指定によるフォントサイズ変更は、viewWillLayoutSubviews()viewDidLayoutSubviews()の間でされるようです。
UIContentSizeCategoryAdjustingプロトコルに対応していないUISegmentedControlなどは、iOS9以前と同様に、次の通知によって対応することになります。

iOS8以降は、traitCollectionDidChangeを使う。

UITraitEnvironmentプロトコルのtraitCollectionDidChangeメソッドがUIViewControlerUIViewで使えます。
iOS13のダークモードの変更にもこのメソッドで対応できます。

class MyViewController : UIViewController {
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        // UIFont.preferredFont() を使ってフォントを設定する。
        hoge1.font = UIFont.preferredFont(forTextStyle: .body)
        ...
    }
}


編集履歴:
* 2020/2/10 traitCollectionDidChangeについて追記。

通知を受けて自分で反映する場合。

Notificationを受け取って処理する場合は以下のようになります。
UIContentSizeCategory.didChangeNotification(swift 4.1まではNSNotification.Name.UIContentSizeCategoryDidChangeでした)によって、Dynamic Typeの動的な変更をキャッチすることができます。これをUIに反映することで、動的なサイズ変更に追従することができます。

var anObserver:NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
    anObserver = NotificationCenter.default.addObserver(forName: UIContentSizeCategory.didChangeNotification /*NSNotification.Name.UIContentSizeCategoryDidChange*/, object: nil, queue: OperationQueue.main, using: updateFont)
}
override func viewWillDisappear(_ animated: Bool) {
    if anObserver != nil {
        NotificationCenter.default.removeObserver(anObserver!)
        anObserver = nil
    }
}
func updateFont(_ notification:NSNotification) {
    // UIFont.preferredFont() を使って全部再設定する。
    hoge1.font = UIFont.preferredFont(forTextStyle: .body)
    ...
}

編集履歴:
* 2018/7/20: 上記コードにobserverの解除ができてないバグがあり、修正しました。なお、iOS9以降はremoveObserverが不要、とAppleが言っていますが、それはビューコントローラーが解放される場合の話のようです。viewWillAppearでobserverを登録する場合、viewWillDisappearremoveObserverする必要があります。そうしないと、UITabBarControllerを使うなどして各コントローラーが解放されない場合、表示する度にobserverが追加されるため、一度の表示で何回も通知を受け取るようになります。
* 2019/3/6: 恥ずかしいミスを修正(obserber -> observer)

通知での設定が優先される

通知の呼び出しは設定からアプリケーションに戻ったところで発生します。その後で、viewWillLayoutSubviews()viewDidLayoutSubviews()が呼び出されます。しかし、通知を受け取ってフォントサイズを変更した場合、その後、adjustsFontForContentSizeCategory = trueによるフォントサイズの自動変更が更に発生して設定を上書きすることはありません。通知による変更は、adjustsFontForContentSizeCategory = trueよりも優先されるようです。
このため、表示崩れを防ぐために、通知の中で標準よりも小さい文字を指定して逃げるような使い方も可能ですし、インターフェースビルダーでのDynamic Typeのチェックも神経質にならずに使うことができます。

iOS9.3シミュレーターのバグ(?)

なお、XCode9.4に付属のiOS9.3のシミュレーターには、動的なサイズ変更が正しく反映されない不具合があるようです。変更は反映されず、通知も発行されません。 文字サイズの設定を変更した後、Cmd+WでシミュレーターのiPhoneを一旦終了すると、次の起動では反映されるようです。
iOS10以降のシミュレーターでは動的な文字サイズの変更をテストできます。

Xcode11のバグ

Xcode11で、ストーリーボードでダイナミックタイプフォントを選択できない場合があるようです。私の環境はXcode 11.3.1ですがこの現象が発生しています。仕方ないので全部コードでフォントを指定しています・・・
Stack Overflowでの議論

設定されるフォントサイズ

iOS9/10の場合

設定のフォントサイズを変更した場合の各pointSizeを表にまとめます。
1から7までが通常指定できる大きさで、デフォルトは4です。
8以降は「さらに大きな文字」スイッチをオンにすることで選べるようになります。

スタイル 1 2 3 4 5 6 7 8 9 10 11 12
.body 14 15 16 17 19 21 23 28 33 40 47 53
.callout 13 14 15 16 18 20 22 22 22 22 22 22
.caption1 11 11 11 12 14 16 18 18 18 18 18 18
.caption2 11 11 11 11 13 15 17 17 17 17 17 17
.footnote 12 12 12 13 15 17 19 19 19 19 19 19
.headline 14 15 16 17 19 21 23 23 23 23 23 23
.subheadline 12 13 14 15 17 19 21 21 21 21 21 21
.title1 25 26 27 28 30 32 34 34 34 34 34 34
.title2 19 20 21 22 24 26 28 28 28 28 28 28
.title3 17 18 19 20 22 24 26 26 26 26 26 26

iOS10までは、さらに大きな文字で大きくなるのは.bodyだけでした。

iOS11の文字サイズ

iOS11ではスタイルが一つ増えただけでなく、個々の文字サイズも変わっています。(太字はiOS10までに対する変更点)

iPhone5/SE系:

スタイル 1 2 3 4 5 6 7 8 9 10 11 12
.body 14 15 16 17 19 21 23 28 33 40 47 53
.callout 13 14 15 16 18 20 22 26 32 38 44 51
.caption1 11 11 11 12 14 16 18 22 26 32 37 43
.caption2 11 11 11 11 13 15 17 20 24 29 34 40
.footnote 12 12 12 13 15 17 19 23 27 33 38 44
.headline 14 15 16 17 19 21 23 28 33 40 47 53
.subheadline 12 13 14 15 17 19 21 25 30 36 42 49
.title1 25 26 26 26 27 28 30 36 41 46 51 56
.title2 19 20 20 20 21 22 24 29 35 41 47 54
.title3 19 20 20 20 21 22 24 29 35 41 47 54
.largeTitle 31 32 32 32 33 34 35 42 46 50 54 58

「さらに大きな文字」をオンにしたときの文字サイズが.body以外も大きくなっています。
(title2とtitle3が同じなのは奇妙ですが、実際そうなっています。)

実際のフォントで図にしてみるとこうなります。
左のタイトルはデフォルト設定での各スタイルの文字で、右側の数字が個々の設定に応じた実際の大きさです。
capture.png

これだけ大きさが違うと簡単には対応できないですよね・・・

追記:2018/7/24 iOS11では、デバイス毎に異なるサイズが使われていることがわかったので以下を追加します。

iPhone6系, 6Plus系, X:

スタイル 1 2 3 4 5 6 7 8 9 10 11 12
.body 14 15 16 17 19 21 23 28 33 40 47 53
.callout 13 14 15 16 18 20 22 26 32 38 44 51
.caption1 11 11 11 12 14 16 18 22 26 32 37 43
.caption2 11 11 11 11 13 15 17 20 24 29 34 40
.footnote 12 12 12 13 15 17 19 23 27 33 38 44
.headline 14 15 16 17 19 21 23 28 33 40 47 53
.subheadline 12 13 14 15 17 19 21 25 30 36 42 49
.title1 25 26 27 28 30 32 34 38 43 48 53 58
.title2 19 20 21 22 24 26 28 34 39 44 50 56
.title3 17 18 19 20 22 24 26 31 37 43 49 55
.largeTitle 31 32 33 34 36 38 40 44 48 52 56 60

こちらも図にしてみました。
iOS11FontSizesOnX.png

終わりに

Dynamic TypeはiOS7で導入され、iOS11でさらに大きな文字に対応するように拡張され、iOSのデフォルトアプリ全体でも対応が進みました。iOS13では設定アプリのアクセシビリティ項目を一つ上の階層に持ってきています。
Appleはもともとアクセシビリティに高い関心を寄せてきた会社ですが、社会的影響力が大きくなって、ますます熱意を注いでいるようです。特にiOS11では非常に大きな文字を指定できるようになりました。これは、Appleの弱視の人々に対する企業姿勢を表しているように思います。

残念ながら対応しているアプリは多くないようですが、こういうところにこそ、企業の姿勢が表れるように思います。特に小さい文字を見にくいと感じる人々にとっては、なくてはならない機能であり、高齢化の進んだ日本では多くのユーザーに影響を及ぼすようにも思います。
対応アプリが増えて、より多くの人にやさしい社会に近づくことを願います。

eytyet
求職中です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした