@ScaledMetric — property wrapper SwiftUI, який автоматично масштабує числове значення відповідно до налаштувань Dynamic Type користувача. Значення обгортається в @ScaledMetric і перераховується при зміні системного розміру шрифту, що гарантує доступність інтерфейсу для людей з вадами зору. За даними Apple Developer Documentation (2026), @ScaledMetric використовує шкалу UIFontMetrics для обчислення відносного масштабу на основі preferred content size category. Докладніше про доступність читайте в матеріалі про доступність 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 на основі базового значення та поточного 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), вкажіть правильний 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також