Dynamic Type — встроенная функция iOS, которая автоматически изменяет размер шрифта в приложении в соответствии с системными настройками пользователя. Пользователь задаёт предпочитаемый размер текста в Настройках → Экран и яркость → Размер текста, и все поддерживающие Dynamic Type приложения адаптируются без дополнительных действий. По данным Apple Human Interface Guidelines, 2024, поддержка Dynamic Type — обязательное требование для публикации в App Store, так как это базовый элемент accessibility на платформе iOS.
Главное
Dynamic Type — технология iOS, появившаяся в iOS 7, которая позволяет тексту в приложении автоматически подстраиваться под системные настройки размера шрифта. Пользователь может увеличить или уменьшить шрифт во всех приложениях сразу — без необходимости менять настройки внутри каждого приложения отдельно.
По данным Apple WWDC 2023, «Make Your App Visually Accessible», более 40% пользователей iOS изменяют размер шрифта от стандартного. Для возрастной группы 65+ этот показатель достигает 70%. Dynamic Type — обязательное требование для accessibility-сертификации App Store. Отсутствие поддержки — причина отклонения приложения на ревью.
Принцип работы: приложение использует UIFont.preferredFont(forTextStyle:) вместо UIFont(name:size:). iOS автоматически возвращает шрифт, масштабированный под текущую системную настройку. При изменении размера в настройках iOS генерирует уведомление UIContentSizeCategory.didChangeNotification — приложение должно пересчитать вёрстку.
Dynamic Type появился в iOS 7 вместе с «плоским» дизайном. В iOS 10 добавили поддержку кастомных шрифтов через UIFontMetrics. iOS 11 ввёл accessibility-уровни (до AccessibilityXXXL). iOS 15 добавил Dynamic Type в SwiftUI с автоматической поддержкой. С каждым релизом Apple усиливает требования: начиная с iOS 17, приложения без поддержки Dynamic Type получают предупреждение при загрузке в App Store Connect.
iOS предоставляет 8 текстовых стилей, каждый с предопределённым размером и начертанием для стандартной настройки:
| Стиль | Константа | Размер (L) | Назначение |
|---|---|---|---|
| Large Title | .largeTitle | 34px | Главный заголовок экрана |
| Title 1 | .title1 | 28px | Заголовок секции |
| Title 2 | .title2 | 22px | Подзаголовок |
| Title 3 | .title3 | 20px | Заголовок карточки |
| Headline | .headline | 17px (bold) | Полужирный для акцентов |
| Body | .body | 17px | Основной текст |
| Callout | .callout | 16px | Вспомогательный текст |
| Caption 1 | .caption1 | 12px | Подпись под изображением |
| Caption 2 | .caption2 | 11px | Мелкая подпись |
| Footnote | .footnote | 13px | Сноска, примечание |
Использование правильного стиля — не просто «внешний вид». UIFont.TextStyle.body в режиме AccessibilityXXXL может достигать 53px. Если приложение использует фиксированный шрифт 17px для body, текст станет нечитаемым для пользователей, увеличивших шрифт.
Dynamic Type не только масштабирует — он сохраняет визуальную иерархию. Large Title всегда крупнее Title 1, который крупнее Body, независимо от уровня масштабирования. Коэффициенты масштабирования различаются: заголовки масштабируются активнее, чем основной текст, чтобы иерархия сохранялась даже на accessibility-уровнях.
iOS поддерживает 11 уровней масштабирования текста, разделённых на две категории:
Разница между XS и AccessibilityXXXL для стиля body составляет от 14px до 53px — почти 4×. Вёрстка, рассчитанная на 17px, полностью ломается на 53px: текст выходит за границы, кнопки перекрываются, ячейки наезжают друг на друга.
Проверить текущую категорию в коде:
let category = UIApplication.shared.preferredContentSizeCategory
// .extraSmall, .small, .medium, .large, .extraLarge ...
if category.isAccessibilityCategory {
// Активиране на алтернативно оформление
}
Категория размера текста доступна через traitCollection.uiContentSizeCategory. При изменении системной настройки iOS вызывает traitCollectionDidChange на всех UIView. В этом методе нужно обновить шрифты и пересчитать вёрстку. SwiftUI делает это автоматически — UIKit требует ручной подписки.
Для UICollectionView используйте UICollectionViewCompositionalLayout — он автоматически подстраивает количество колонок под ширину экрана и размер текста. На accessibility-уровнях переключайтесь с двухколоночного на одноколоночный макет, чтобы текст не обрезался и элементы не наезжали друг на друга. Используйте conditional layout: для regular width — две колонки, для compact или accessibility — одна колонка.
Базовая реализация в UIKit — UIFont.preferredFont(forTextStyle:). Этот метод возвращает шрифт, масштабированный под текущую системную настройку:
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)
Для кастомных шрифтов используйте UIFontMetrics:
let customFont = UIFont(name: "Montserrat-Regular", size: 16)!
titleLabel.font = UIFontMetrics(forTextStyle: .body)
.scaledFont(for: customFont)
// Проследяване на промените в размера
NotificationCenter.default.addObserver(
self,
selector: #selector(preferredContentSizeChanged),
name: UIContentSizeCategory.didChangeNotification,
object: nil
)
Для UILabel в Interface Builder достаточно установить шрифт как текстовый стиль (Text Style) и включить «Automatically Adjusts Font». Для кастомных шрифтов IB не поддерживает UIFontMetrics — только через код.
UIFontMetrics использует ту же scaling curve, что и preferredFont. Вы можете задать fromTextStyle: .body, .headline и т.д. Каждый стиль имеет свой коэффициент масштабирования. Body масштабируется умеренно, Large Title — агрессивно. Если ваш кастомный шрифт используется для основного текста, используйте .body.
В SwiftUI поддержка Dynamic Type встроена по умолчанию. Все системные модификаторы (.font(.body), .font(.title)) автоматически масштабируются. Разработчику не нужно вызывать UIFont.preferredFont.
Пример:
Text("Основен текст")
.font(.body)
.lineLimit(nil)
.minimumScaleFactor(0.5)
Для кастомных масштабов используйте DynamicTypeSize:
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
Text("Адаптивен текст")
.font(.body)
.padding(dynamicTypeSize <= .large ? 8 : 16)
}
SwiftUI автоматически обновляет представление при изменении системного размера текста — никаких дополнительных подписок на уведомления.
Модификатор .font(.body) работает только для системного шрифта. Для кастомных шрифтов в SwiftUI используйте Font.custom с UIFontMetrics под капотом. Начиная с iOS 16, SwiftUI поддерживает DynamicTypeSize в @Environment, что позволяет адаптировать отступы и компоновку.
Auto Layout — обязательное условие поддержки Dynamic Type. Фиксированные ширины и высоты (>=, <=) ломаются при accessibility-размерах. Используйте intrinsicContentSize для UILabel — он автоматически вычисляет высоту под текущий размер шрифта.
Правила адаптивной вёрстки:
Для длинных текстов (например, статьи, условия использования) установите minimumScaleFactor на UILabel — это уменьшает текст в последнюю очередь, когда Auto Layout не может вместить контент. Значение 0.5 означает, что шрифт может уменьшиться до 50% от предпочтительного размера.
Пример подстройки отступов под категорию размера:
let isAccessibility = traitCollection
.preferredContentSizeCategory.isAccessibilityCategory
stackView.spacing = isAccessibility ? 16 : 8
button.contentEdgeInsets = isAccessibility
? UIEdgeInsets(top: 16, left: 24, bottom: 16, right: 24)
: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
Обрезание текста — UILabel с numberOfLines = 1 и фиксированной шириной обрезает текст на AccessibilityXL. Решение: numberOfLines = 0 и trailing constraint. Перекрытие элементов — кнопки с фиксированной высотой наезжают друг на друга. Решение: UIStackView с automatic spacing. Выход за границы экрана — ScrollView обязателен для accessibility-уровней.
Симулятор Xcode позволяет сменить размер текста: в меню симулятора Settings → Accessibility → Display & Text Size → Larger Text. Переключайте между всеми 11 уровнями и проверяйте, что текст не обрезается, кнопки доступны, вёрстка не ломается.
Для автоматизированного тестирования используйте настройку contentSizeCategory в XCUIApplication:
func testAccessibilitySizes() {
let app = XCUIApplication()
app.launchArguments += [
"-UIPreferredContentSizeCategoryName",
"UICTContentSizeCategoryAccessibilityXXXL"
]
app.launch()
app.scrollViews.buttons["Изпращане"].tap()
XCTAssertTrue(app.staticTexts["Формата е изпратена"]
.waitForExistence(timeout: 5))
}
XCUITest с разными launchArguments позволяет проверить все уровни масштабирования в CI. Не забудьте проверить accessibility-категории отдельно — они чаще всего ломают вёрстку. По данным Apple, 70% багов Dynamic Type обнаруживаются именно на accessibility-уровнях, потому что разработчики тестируют только стандартные размеры.
Xcode Accessibility Inspector показывает, какие шрифты использует выбранный элемент — фиксированный или масштабируемый. Если элемент не реагирует на изменение размера текста в симуляторе, значит используется фиксированный шрифт. Замените на UIFont.preferredFont или UIFontMetrics.
Часто задаваемые вопросы
Это автоматическое масштабирование текста в приложении под системные настройки размера. Пользователь меняет размер в Настройках — все поддерживающие приложения подстраиваются без участия разработчика.
Нет. Для кастомных шрифтов используйте UIFontMetrics(scaledFont:). Метод масштабирует любой шрифт относительно указанного текстового стиля, сохраняя пропорции.
11 уровней: 5 стандартных (XS, S, M, L, XL) и 6 accessibility-уровней (от AccessibilityXL до AccessibilityXXXXXL). Размер шрифта body может варьироваться от 14px до 53px.
В UIKit через UIContentSizeCategory.didChangeNotification. В SwiftUI используйте @Environment(\.dynamicTypeSize) — представление обновится автоматически при изменении системной настройки.
Пользователи с увеличенным шрифтом увидят обрезанный текст, наезжающие элементы и неработающие кнопки. Приложение может быть отклонено на App Store Review за нарушение accessibility-требований.
Итоги
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също