@ScaledMetric — property wrapper SwiftUI, который автоматически масштабирует числовое значение в соответствии с настройками Dynamic Type пользователя. Значение оборачивается в @ScaledMetric и пересчитывается при изменении системного размера шрифта, что гарантирует доступность интерфейса для людей с нарушениями зрения. По данным Apple Developer Documentation (2026), @ScaledMetric использует шкалу UIFontMetrics для вычисления относительного масштаба на основе preferred content size category. Подробнее об accessibility читайте в материале об accessibility SwiftUI.
Главное
@ScaledMetric — property wrapper SwiftUI, добавленный в iOS 14, который автоматически масштабирует числовое значение (CGFloat, Int, Double) под текущий размер шрифта Dynamic Type. В отличие от .font(.body) для шрифтов, @ScaledMetric масштабирует любые числовые параметры: padding, spacing, cornerRadius, iconSize — всё, что должно пропорционально увеличиваться при крупном тексте.
Основная задача @ScaledMetric — обеспечить accessibility-масштабирование не-текстовых элементов интерфейса. Когда пользователь увеличивает шрифт в настройках iOS, кнопки, иконки и отступы должны увеличиваться пропорционально, чтобы интерфейс оставался сбалансированным. @ScaledMetric решает эту задачу автоматически, без ручного вычисления множителей.
Базовый синтаксис @ScaledMetric использует значение по умолчанию и необязательный параметр relativeTo. Если relativeTo указан, масштабирование привязано к конкретному текстовому стилю (UIFontTextStyle). Если нет — используется шкала .body.
struct AccessibleButton: View {
@ScaledMetric private var padding: CGFloat = 12
@ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = 24
var body: some View {
Label("Submit", systemImage: "checkmark.circle.fill")
.font(.body)
.padding(padding)
.imageScale(.init(rawValue: iconSize / 24) ?? .medium)
}
}
padding будет масштабироваться относительно .body (по умолчанию), iconSize — относительно .title. При крупном тексте отступы и иконка увеличатся пропорционально. Без @ScaledMetric отступы остались бы 12 pt при любом размере шрифта, что привело бы к визуальному дисбалансу.
Механизм @ScaledMetric основан на UIFontMetrics из UIKit. Когда SwiftUI создаёт экземпляр @ScaledMetric, он вычисляет множитель на основе текущей preferred content size category (UIContentSizeCategory). Базовое значение умножается на scaledValue из UIFontMetrics для указанного текстового стиля.
Математически: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Если relativeTo не указан, используется UIFontMetrics.default, привязанный к .body. При изменении Dynamic Type SwiftUI пересоздаёт тело View, @ScaledMetric вычисляет новый scaledValue и UI обновляется автоматически через механизм @State-подобных PropertyWrappers.
Шкала iOS включает 11 размеров: от .extraSmall (5 pt) до .accessibilityExtraExtraExtraLarge (77 pt для .body). Коэффициент масштабирования для .body варьируется от 0.85 (XS) до 1.71 (XXXL) относительно базового значения. @ScaledMetric использует именно эту шкалу, поэтому значение 12 pt для padding может стать ~20 pt при максимальном accessibility-размере.
| Content Size Category | Коэффициент (body) | Пример @ScaledMetric(12) |
|---|---|---|
| extraSmall | 0.85 | ~10 pt |
| small | 0.93 | ~11 pt |
| medium (default) | 1.00 | 12 pt |
| large | 1.07 | ~13 pt |
| extraLarge | 1.15 | ~14 pt |
| extraExtraLarge | 1.28 | ~15 pt |
| accessibilityExtraLarge | 1.47 | ~18 pt |
| accessibilityXXXL | 1.71 | ~20 pt |
Выбор relativeTo: используйте .body для значений, связанных с основным текстом (padding, spacing в списках), .title для крупных элементов (iconSize, imageSize), .caption для мелких элементов (badge размер). Это гарантирует, что элементы масштабируются в ногу с окружающим текстом.
Dynamic Type — функция iOS, которая позволяет пользователю настроить размер шрифта системы в Settings → Display & Brightness → Text Size. Изменение применяется глобально ко всем приложениям. @ScaledMetric реагирует на это изменение автоматически: SwiftUI обновляет все @ScaledMetric-переменные при изменении UIContentSizeCategory.
Важно: @ScaledMetric масштабирует только числовые значения, но не управляет шрифтами напрямую. Для шрифтов используйте .font() с текстовым стилем (.body, .title, .headline) — SwiftUI автоматически масштабирует шрифт. @ScaledMetric дополняет шрифтовое масштабирование для padding, spacing и размеров элементов.
Canvas Preview поддерживает Dynamic Type: в панели инструментов Canvas есть ползунок Text Size (A–A) для проверки UI при разных размерах шрифта. Используйте его с @ScaledMetric, чтобы убедиться, что отступы и размеры масштабируются правильно.
struct CardView: View {
@ScaledMetric private var cornerRadius: CGFloat = 16
@ScaledMetric private var spacing: CGFloat = 8
var body: some View {
VStack(spacing: spacing) {
Text("Card Title")
.font(.headline)
Text("Description with dynamic type support")
.font(.body)
}
.padding(spacing * 2)
.background(.regularMaterial)
.cornerRadius(cornerRadius)
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView()
.dynamicTypeSize(.large)
.previewDisplayName("Large")
CardView()
.dynamicTypeSize(.accessibility5)
.previewDisplayName("Accessibility 5")
}
}
cornerRadius масштабируется от 16 pt до ~27 pt при максимальном accessibility-размере. spacing — от 8 до ~14 pt. Это гарантирует, что карточка остаётся визуально сбалансированной при любом размере шрифта.
Пример: иконка с поддержкой Dynamic Type. Размер иконок Image(systemName:) по умолчанию не масштабируется под Dynamic Type. @ScaledMetric решает эту проблему: изменяя imageScale или frame размер на основе current scale factor.
struct IconLabel: View {
let title: String
let icon: String
@ScaledMetric private var iconDimension: CGFloat = 28
@ScaledMetric(relativeTo: .body) private var spacing: CGFloat = 6
var body: some View {
HStack(spacing: spacing) {
Image(systemName: icon)
.resizable()
.frame(width: iconDimension, height: iconDimension)
Text(title)
.font(.body)
}
}
}
Пример: доступный badge-компонент. Badge с числом должен масштабироваться пропорционально тексту. @ScaledMetric для минимального размера badge гарантирует, что круглый badge остаётся заметным при крупном шрифте.
struct BadgeView: View {
let count: Int
@ScaledMetric(relativeTo: .caption) private var badgeSize: CGFloat = 20
@ScaledMetric(relativeTo: .caption) private var fontScale: CGFloat = 1
var body: some View {
ZStack {
Circle()
.fill(.red)
.frame(width: badgeSize, height: badgeSize)
Text("\(count)")
.font(.caption)
.foregroundColor(.white)
.scaleEffect(fontScale)
}
.fixedSize()
}
}
fontScale дополнительно масштабирует содержимое Circle для соответствия увеличенному badgeSize. Без fontScale текст внутри badge мог бы не поместиться при крупном шрифте.
@ScaledMetric и @State — оба property wrappers, отслеживающие изменения, но с разными источниками обновлений. @State обновляет значение при программном изменении (через $stateBinding). @ScaledMetric обновляет значение автоматически при изменении системного Dynamic Type, но не позволяет изменять значение из кода напрямую.
Ключевое отличие: @ScaledMetric — read-only для разработчика и write-only для системы. Вы не можете изменить scaledValue через setter — он вычисляется SwiftUI на основе базового значения и current Dynamic Type. @State, напротив, полностью управляется разработчиком. Если вам нужно значение, которое и масштабируется под Dynamic Type, и изменяется программно — комбинируйте @ScaledMetric с @State или используйте вычисляемое свойство.
| Характеристика | @ScaledMetric | @State |
|---|---|---|
| Источник обновления | Dynamic Type (система) | Программно (разработчик) |
| Тип значения | CGFloat, Int, Double | Любой |
| Изменение из кода | Нельзя | Можно через binding |
| Перерисовка View | При изменении Dynamic Type | При изменении значения |
| iOS версия | iOS 14+ | iOS 13+ |
Комбинированный паттерн: если нужно изменять padding программно (например, анимация нажатия) и одновременно масштабировать под Dynamic Type, создайте @ScaledMetric для базового scaled-значения и @State для множителя анимации. Финальное значение = scaledValue × animationMultiplier.
Ошибка 1: использование @ScaledMetric для шрифтов. @ScaledMetric масштабирует числа, а не шрифты. Для шрифтов используйте .font(.body) — SwiftUI автоматически применяет Dynamic Type. Никогда не используйте @ScaledMetric с font(.system(size: scaledSize)) — это ломает системное accessibility.
Ошибка 2: отсутствие relativeTo для разнородных элементов. Если у вас padding (связан с .body) и iconSize (связан с .title), укажите correct relativeTo для каждого. Без relativeTo оба будут масштабироваться по .body, что даст непропорциональное увеличение иконки относительно её текстового контекста.
Ошибка 3: @ScaledMetric в ViewModel/@ObservableObject. @ScaledMetric — SwiftUI property wrapper, работающий только внутри View. Его нельзя использовать в ViewModel или сервисах. Для масштабирования в ViewModel передавайте scaled-значение из View как параметр или используйте @Environment(\.sizeCategory) в View.
@Environment(\.sizeCategory) — альтернативный способ получения текущего Dynamic Type в View. Используйте его, когда нужно больше контроля: вычислить кастомный множитель, передать sizeCategory в ViewModel или скомбинировать с @ScaledMetric для гибкого масштабирования.
struct CustomScaledView: View {
@Environment(\.sizeCategory) private var sizeCategory
@ScaledMetric private var basePadding: CGFloat = 12
private var extraPadding: CGFloat {
if sizeCategory >= .accessibilityLarge {
return basePadding * 0.5
}
return 0
}
var body: some View {
Text("Custom scaled content")
.font(.body)
.padding(basePadding + extraPadding)
}
}
Дополнительный отступ extraPadding добавляется только при accessibility-размерах, давая больше воздуха для крупного текста без изменения базовой @ScaledMetric логики.
Часто задаваемые вопросы
@ScaledMetric — официальный property wrapper SwiftUI для масштабирования чисел. @ScaledFont не существует как стандартный API — это кастомный wrapper, реализованный сообществом. Для шрифтов всегда используйте встроенный .font() с текстовыми стилями (.body, .title), а @ScaledMetric — для padding, spacing и размеров.
@ScaledMetric доступен на iOS 14+, watchOS 7+, tvOS 14+ и macOS 11+. На watchOS Dynamic Type ограничен меньшим диапазоном — доступны размеры от .extraSmall до .extraLarge без accessibility-размеров. На tvOS Dynamic Type отсутствует — @ScaledMetric всегда возвращает базовое значение.
Да, для тестирования @ScaledMetric создайте View с @ScaledMetric и передайте environment-значение .sizeCategory через .environment(\.sizeCategory, .extraExtraLarge). Затем получите размер элемента через GeometryReader или SwiftUI Inspector. Альтернативно проверяйте логику масштабирования через UIFontMetrics в отдельном модуле.
.dynamicTypeSize — модификатор View, ограничивающий максимальный Dynamic Type для иерархии (например, .dynamicTypeSize(...large)). @ScaledMetric учитывает это ограничение: если .dynamicTypeSize установлен, scaled-значение не превысит соответствующий размер. Комбинируйте оба API для точного контроля.
Убедитесь, что View использует @ScaledMetric внутри себя (не в ViewModel). Проверьте, что View подписывается на Dynamic Type: @ScaledMetric автоматически вызывает body refresh, но если View использует .equatable() или .id(), механизм может сломаться. Используйте @Environment(\.sizeCategory) как fallback.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также