🅰️

ダイナミックタイプとテキストコンポーネントの対応

2024/11/26に公開

ダイナミックタイプ?

ダイナミックタイプ(Dynamic Type)はアクセシビリティ目的でデフォルトの文字サイズを変更できる機能です。テキストのサイズは段階的に変更することができ最大で12段階の変更が可能です。デフォルトでは Large が選択されています。大きさに関する仕様についてはHIGのTypographyで確認できます。
https://developer.apple.com/design/human-interface-guidelines/typography#Specifications

アプリ側の対応

ダイナミックタイプの対応はユーザー体験的に良いことで、開発者目線でもより多くのユーザーを取り込む上で大切なことです。以下でデザインパターンごとの対応方法を紹介します。

システムフォント + スタイル

Appleの提供するスタイルを利用することでダイナミックタイプに対応することができます。フォントを含めたデザインがシステムのものを利用する場合にはこれでOKです🙆‍♂️

Text("Hello, World!")
    .font(.title)

システムフォント + 自由なサイズ

上記のスタイルを利用する場合、詳細なサイズ指定(pt)はできません。システムのフォントを利用しつつサイズ指定する場合

Text("Hello, World!")
    .font(.system(size: 28))

このようにしますが、このフォントはダイナミックタイプに対応していません。そこで ScaledMetric プロパティラッパーを利用します。

@ScaledMetric(relativeTo: .title) var size: CGFloat = 28

Text("Hello, World!")
    .font(.system(size: size))

https://developer.apple.com/documentation/swiftui/scaledmetric

ScaledMetricの数値はダイナミックタイプに対応して変化します。変化率は relativeTo で指定したスタイルに応じて変化します。上記の例では title のスタイルの変化率で 28 をしているので、ダイナミックタイプがxxxLargeになったときに 34 にスケールします。変化率の仕様については冒頭のHIGを参照してください。

カスタムフォント

カスタムフォントを利用する場合はScaledMetricが統合されたようなAPIで提供されています。

Text("Hello, World!")
    .font(.custom("HiraginoSans-W3", size: 28, relativeTo: .title))

システムフォントにも用意してほしいですね🤔

あえて対応しない

ダイナミックタイプへの対応は基本的に良いことずくめですが、UIの崩れが発生することが多く、
(個人的には残念ですが) 対応したくないアプリもあると思います。そのためのいくつかの方法を以下に紹介します。

スタイル

スタイルは利用したいけどダイナミックタイプには対応したくない場合、 dynamicTypeSize(_:) が利用できます。このAPIはViewのダイナミックタイプを固定することができます。

Text("Hello, World!")
    .font(.title)
    .dynamicTypeSize(.large) // ユーザーの設定を無視してLargeになる

LargeはiOS/iPadOSのデフォルトのサイズのため、実質ダイナミックタイプに対応しないことになります。

システムフォント

対応するパターンでも出てきましたが、システムフォントを利用する場合以下の方法でオッケーです🙆‍♀️

Text("Hello, World!")
    .font(.system(size: 28))

カスタムフォント

カスタムフォントを利用する場合は custom(_:fixedSize:) が利用できます。

Text("Hello, World!")
    .font(.custom("HiraginoSans-W3", fixedSize: 28))

(番外編)ダイナミックタイプの範囲を制限する

デザインの都合上、ダイナミックタイプによるサイズ変動の幅を制限したい時があります。e.g. デザインシステムの要件でデフォルトサイズの2倍までは拡大させたい。

ダイナミックタイプを基準に制限する

こういうときに dynamicTypeSize<T>(_:) が利用できます。上記では同じ名前のAPIをダイナミックタイプの固定のために利用しましたが、こちらはレンジ指定することができます。

Text("Hello, World!")
    .font(.title)
    .dynamicTypeSize(DynamicTypeSize.large...DynamicTypeSize.xxLarge)

この例ではダイナミックタイプによってLargeより小さくならず、xxLargeよりも大きくならなくなります。

自由なサイズで制限する

ダイナミックタイプ基準の制限では正確なフォントサイズでの制限ができません。ほぼ力技にはなりますが ScaledMetric を利用することで正確に制限することができます💪

let defaultSize: CGFloat = 28
@ScaledMetric(relativeTo: .title) var scaledSize: CGFloat = 28
var size: CGFloat {
    var scale = scaledSize / defaultSize
    let minScale: CGFloat = 1
    let maxScale: CGFloat = 1.5
    scale = max(scale, minScale)
    scale = min(size, maxScale)
    return defaultSize * scale
}

Text("Hello, World!")
    .font(.system(size: size))

この例では最小と最大のスケールを設定することで 28 ~ 42 のサイズになるように制限しています。

まとめ

ダイナミックタイプにはユーザーからの需要は確かにあり対応したいところです。実際に私の携わっているアプリでは結構な需要があったりします。
しかしそもそもの扱いが難しいことと、デザイン仕様がしっかりするほど対応がしにくいという難点があります。デザイナーさんとのすり合わせを密に行って、そういう文化を根付かせるのが大事なのかなと思ったりします😌

Discussion