ハイライト
Dynamic Type は Apple プラットフォームの文字サイズ自動調整メカニズムです。ユーザーは設定で 7 段階の通常サイズと 5 段階の支援機能用大サイズから選べます。アプリはすべてのサイズに対応する必要があります。この Session では、文字スケーリング、レイアウト調整、画像・シンボル処理、Large Content Viewer によるフォールバックの 4 層で適応戦略を解説します。
主要内容
固定 17pt の文字と HStack による水平レイアウトでアプリを作ったとします。ほとんどのユーザーは不満を言いません。ある日、ユーザーがシステム文字サイズを最大の支援機能レベルに設定したとき、タイトルが切り詰められ、ボタンの文字は半分しか見えず、水平に並んだ 4 つのアイコンが固まってしまいます。そのユーザーはアプリを閉じました。
Dynamic Type はこの問題を解決するメカニズムです。ユーザーは「設定 > アクセシビリティ > 表示と文字サイズ > より大きな文字」で好みのサイズを選びます。システムは 7 段階の通常サイズを提供し、「より大きな支援機能サイズ」を有効にするとさらに 5 段階の大きいオプションが追加されます。アプリがシステム Text Styles を使えば、文字はユーザーの選択に自動的に追従します。固定サイズをハードコードしている場合、ユーザー設定は完全に無視されます。
この Session では 4 層で適応を解説します。第一に、固定サイズをシステム Text Styles に置き換える。第二に、大きなサイズで水平配置が収まらなくなったら垂直レイアウトに切り替える。第三に、大きなサイズでの画像と SF Symbols のスケーリング戦略。第四に、Tab Bar のように文字サイズに追従できないコントロールには Large Content Viewer をフォールバックとして使う。
詳細
1. システム Text Styles で文字をスケーリング
SwiftUI では .font() 修飾子で Text Style を指定します(03:53):
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
.font(.title)
}
}
キーポイント:
.font(.title)はシステム title 文字スタイルを使い、Dynamic Type に自動追従する- システムは body、headline、title など複数のスタイルを提供し、それぞれ視覚的階層を維持する
UIKit では preferredFont(forTextStyle:) を使い、adjustsFontForContentSizeCategory を設定します(04:06):
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: .zero)
setupConstraints()
label.text = "Hello, World!"
label.adjustsFontForContentSizeCategory = true
label.font = .preferredFont(forTextStyle: .title1)
label.numberOfLines = 0
self.view.addSubview(label)
}
}
キーポイント:
adjustsFontForContentSizeCategory = trueでシステム文字サイズ変更時にラベルのフォントが自動更新されるnumberOfLines = 0で任意行数を許可し、切り詰めを防ぐpreferredFont(forTextStyle:)は対応スタイルの動的フォントを返す
問題検出ツール: Xcode Preview で Variants ボタンをクリックし、Dynamic Type Variants を選べばすべてのサイズ変体を一度にプレビューできます。Xcode デバッガーで Dynamic Type 設定を上書きすることも、支援機能監査で文字切り詰め・クリッピング・コントラスト問題を確認することもできます(04:36)。
2. 動的レイアウト切り替え
文字サイズが支援機能範囲に入ると、水平レイアウトが収まらなくなることがあります。SwiftUI では dynamicTypeSize 環境変数と AnyLayout で動的切り替えを行います(07:20):
import SwiftUI
struct FigureCell: View {
@Environment(\.dynamicTypeSize)
private var dynamicTypeSize: DynamicTypeSize
var dynamicLayout: AnyLayout {
dynamicTypeSize.isAccessibilitySize ?
AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
}
let systemImageName: String
let imageTitle: String
var body: some View {
dynamicLayout {
FigureImage(systemImageName: systemImageName)
FigureTitle(imageTitle: imageTitle)
}
}
}
キーポイント:
@Environment(\.dynamicTypeSize)で現在の文字サイズを読み取るisAccessibilitySizeで支援機能の大サイズかどうかを判定するAnyLayoutで実行時に HStackLayout と VStackLayout を切り替えられる- 単一 cell は支援機能サイズで上下(VStack)から左右(HStack)レイアウトに切り替わる
外側のコンテナも同期して切り替える必要があります(07:52):
import SwiftUI
struct FigureContentView: View {
@Environment(\.dynamicTypeSize)
private var dynamicTypeSize: DynamicTypeSize
var dynamicLayout: AnyLayout {
dynamicTypeSize.isAccessibilitySize ?
AnyLayout(VStackLayout(alignment: .leading)) : AnyLayout(HStackLayout(alignment: .top))
}
var body: some View {
dynamicLayout {
FigureCell(systemImageName: "figure.stand", imageTitle: "Standing Figure")
FigureCell(systemImageName: "figure.wave", imageTitle: "Waving Figure")
FigureCell(systemImageName: "figure.walk", imageTitle: "Walking Figure")
FigureCell(systemImageName: "figure.roll", imageTitle: "Rolling Figure")
}
}
}
キーポイント:
- 外側は支援機能サイズで HStack から VStack に切り替わり、各 cell が画面幅いっぱいになる
alignmentパラメータで各レイアウトに応じた配置を設定する
UIKit では UIStackView を使います(08:20):
import UIKit
class ViewController: UIViewController {
private var mainStackView: UIStackView = UIStackView()
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(textSizeDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
setupStackView()
}
@objc private func textSizeDidChange(_ notification: Notification?) {
let isAccessibilityCategory = self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory
mainStackView.axis = isAccessibilityCategory ? .vertical : .horizontal
setupConstraints()
}
}
キーポイント:
UIContentSizeCategory.didChangeNotificationを監視して文字サイズ変更に応答するisAccessibilityCategoryで支援機能サイズかどうかを判定するUIStackView.axisを変更するだけで水平/垂直を切り替えられる
3. 画像と SF Symbols のスケーリング戦略
装飾用画像は拡大に追従すべきではありません。設定アプリの各リスト項目左側の小アイコンは大サイズでも元のサイズを保ち、文字はアイコンの周りに折り返されます。SwiftUI の List はこの折り返しを自動処理します(10:12)。UIKit では NSAttributedString + NSTextAttachment でインライン画像を実装し、文字が自然に回り込みます(10:30)。
画像を拡大に追従させる必要がある場合(文字や重要アイコンを含む画像など)、SwiftUI では @ScaledMetric を使います(11:05):
import SwiftUI
struct ContentView: View {
@ScaledMetric var imageWidth = 125.0
var body: some View {
VStack {
Image("Spatula")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: imageWidth)
Text("Grill Party!")
.frame(alignment: .center)
}
}
}
キーポイント:
@ScaledMetricプロパティラッパーで値が Dynamic Type に自動スケールする- 基準幅 125pt を指定し、実行時に文字サイズに応じて調整される
- SF Symbols は
@ScaledMetric不要。システムが自動スケールする
UIKit では UIImage.SymbolConfiguration と textStyle を組み合わせます(11:38):
import UIKit
func imageWithBodyConfiguration(systemImageName: String) -> UIImage? {
let imageConfiguration = UIImage.SymbolConfiguration(textStyle: .body)
let configuredImage = UIImage(systemName: systemImageName, withConfiguration: imageConfiguration)
return configuredImage
}
キーポイント:
SymbolConfiguration(textStyle: .body)で SF Symbol が body スタイルと同期してスケールする- Symbol 以外のカスタム画像は
UITraitCollectionで手動スケーリングが必要
4. Large Content Viewer フォールバック
Tab Bar のような常駐コントロールは文字サイズに追従して拡大できません。拡大すると Tab Bar が画面の 4 分の 1 を占めます。システム Tab Bar には Large Content Viewer が組み込まれています。カスタムコントロールには手動追加が必要です。
SwiftUI では .accessibilityShowsLargeContentViewer 修飾子を使います(13:15):
import SwiftUI
struct FigureBar: View {
@Binding var selectedFigure: Figure
var body: some View {
HStack {
ForEach(Figure.allCases) { figure in
FigureButton(figure: figure, isSelected: selectedFigure == figure)
.onTapGesture {
selectedFigure = figure
}
.accessibilityShowsLargeContentViewer {
Label(figure.imageTitle, systemImage: figure.systemImage)
}
}
}
}
}
キーポイント:
.accessibilityShowsLargeContentViewerはクロージャを受け取り、長押し時に表示する拡大ビューを返す- クロージャ内で
Labelを使い、文字とアイコンの両方を提供する - 長押しで中央に拡大表示が現れ、スワイプで隣接コントロールに切り替えられる
UIKit では UILargeContentViewerInteraction を使います(13:45):
import UIKit
class FigureCell: UIStackView {
var systemImageName: String!
var imageTitle: String!
init(systemImageName: String, imageTitle: String) {
super.init(frame: .zero)
self.systemImageName = systemImageName
self.imageTitle = imageTitle
setupFigureCell()
self.addInteraction(UILargeContentViewerInteraction())
self.showsLargeContentViewer = true
self.largeContentImage = UIImage(systemName: systemImageName)
self.scalesLargeContentImage = true
self.largeContentTitle = imageTitle
}
}
キーポイント:
addInteraction(UILargeContentViewerInteraction())でインタラクションを追加するshowsLargeContentViewer = trueで表示を有効化するlargeContentImageとlargeContentTitleでポップアップ内のアイコンと文字を設定するscalesLargeContentImage = trueでポップアップ内のアイコンを拡大する
重要ポイント
-
何をするか: すべての固定サイズをシステム Text Styles に置き換える。なぜ価値があるか: Dynamic Type 対応の第一歩で、変更は小さく効果は大きい。文字がすぐにユーザー設定に追従する。始め方: SwiftUI では
.font(.system(size: 17))を.font(.body)/.font(.title)に置き換える。UIKit ではUIFont.systemFont(ofSize:)をpreferredFont(forTextStyle:)に置き換え、adjustsFontForContentSizeCategory = trueを設定する。 -
何をするか: 水平配置のカード型レイアウトに「水平から垂直へ」の切り替え能力を持たせる。なぜ価値があるか: 支援機能大サイズでは水平スペースが足りず文字が切り詰められる。垂直レイアウトで各項目が幅いっぱいになり、読みやすくなる。始め方: SwiftUI では
@Environment(\.dynamicTypeSize)+AnyLayoutでisAccessibilitySizeに応じて切り替える。UIKit ではUIStackViewでdidChangeNotificationを監視して axis を切り替える。 -
何をするか: カスタム Tab Bar とツールバーに Large Content Viewer を追加する。なぜ価値があるか: これらのコントロールは文字サイズに追従できず、大サイズユーザーが長押ししてもコンテンツラベルが見えず、ナビゲーション能力を失う。始め方: SwiftUI では
.accessibilityShowsLargeContentViewer { Label(...) }を追加。UIKit ではUILargeContentViewerInteractionを追加しlargeContentTitleとlargeContentImageを設定する。 -
何をするか: UI テストに支援機能監査を組み込む。なぜ価値があるか: 各イテレーションで文字切り詰め、クリッピング、低コントラストなど Dynamic Type 問題を自動検出でき、手動のページごと確認が不要になる。始め方: XCTest で支援機能監査 API を呼び出し、異なる Dynamic Type 設定でテストを実行する。
関連セッション
- Catch up on accessibility in SwiftUI — VoiceOver、Switch Control など支援技術を網羅した SwiftUI アクセシビリティ入門
- Demystify SwiftUI containers — SwiftUI コンテナビューの子ビュー管理モデルを理解し、動的レイアウト設計に役立てる
- What’s new in UIKit — テキストと入力関連の改善を含む UIKit の新機能
- Build multilingual-ready apps — Dynamic Type 適応と相補的な多言語文字表示のベストプラクティス
コメント
GitHub Issues · utterances