Implementing Dynamic Type (Font Scaling) Support in iOS
Dynamic Type is an iOS mechanism that allows users to set preferred text size in Settings → Accessibility → Display & Text Size → Larger Text. Range: from xSmall to xxxLarge (7 standard + 5 additional "accessibility" sizes via Larger Text slider with Accessibility Sizes enabled). Largest size — AX5 — increases font roughly 3x from baseline.
If application doesn't support Dynamic Type, users with low vision are forced to use Zoom (screen-wide scaling) — worse UX. Dynamic Type support is the first and most important accessibility step.
How to Properly Enable It
UIKit
UIFont.preferredFont(forTextStyle: .body) — font with Dynamic Type support from system table. Automatically scales when setting changes. adjustsFontForContentSizeCategory = true on UILabel, UITextField, UITextView — enables real-time response to changes (without app restart).
For custom fonts: UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont). UIFontMetrics scales custom font proportionally to system table for selected TextStyle.
Common mistake: set font via UIFont(name: "CustomFont-Regular", size: 17) without UIFontMetrics — size is fixed, Dynamic Type doesn't work.
SwiftUI
Font.body, Font.headline, Font.caption — system fonts with Dynamic Type out of the box. For custom: Font.custom("CustomFont-Regular", size: 17, relativeTo: .body) — relativeTo parameter enables scaling.
@ScaledMetric — for scaling non-font values (spacing, icons):
@ScaledMetric(relativeTo: .body) var iconSize: CGFloat = 24
At AX5 icon grows to ~72pt. Important: icons and spacing must scale with text, else layout breaks.
Where Layout Breaks at Large Sizes
Fixed row heights and containers. UILabel with numberOfLines = 1 and fixed height — text gets cut off. At AX5 a 20-character title occupies 3 lines. Solution: remove fixed height constraints on text elements, use numberOfLines = 0 where possible.
Horizontal stacks with text. UIStackView with axis = .horizontal and two UILabel — at large font labels don't fit side by side. Need to either switch axis to .vertical at large sizes via traitCollectionDidChange, or design vertical layout initially.
In SwiftUI: ViewThatFits (iOS 16+) — selects layout based on available space. For earlier versions — @Environment(\.sizeCategory) + conditional HStack/VStack.
Tables and collections. UITableViewCell with Auto Layout and no fixed height — fine. With tableView.rowHeight = 44 — at AX5 content won't fit. tableView.rowHeight = UITableView.automaticDimension is mandatory.
Buttons with icon and text. At AX3+ text can wrap and button resizes — can break neighbor layout.
Testing
Xcode Simulator: change font size through Settings directly in simulator. Or Environment Overrides (Xcode Debug → Simulate Dynamic Type) — more convenient during development.
Must test accessibility sizes (AX1-AX5) — many teams test only standard range and miss extreme-size issues.
Snapshot-tests with fixed ContentSizeCategory: UITraitCollection(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge) — add to CI to catch layout regressions automatically.
Timeframe: 2-3 days for typical application. Main work — fixing layout constraints and custom components. Cost calculated after audit.







