@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 е да осигури мащабиране за достъпност на нетекстови елементи на интерфейса. Когато потребителят увеличи шрифта в настройките на 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("Изпращане", 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 се обновява автоматично чрез механизма на PropertyWrappers, подобни на @State.
iOS скала включва 11 размера: от .extraSmall (5 pt) до .accessibilityExtraExtraExtraLarge (77 pt за .body). Коефициентът на мащабиране за .body варира от 0.85 (XS) до 1.71 (XXXL) спрямо базовата стойност. @ScaledMetric използва именно тази скала, така че стойност 12 pt за padding може да стане ~20 pt при максимален размер за достъпност.
| 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("Заглавие на карта")
.font(.headline)
Text("Описание с поддръжка на dynamic type")
.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 при максимален размер за достъпност. spacing — от 8 до ~14 pt. Това гарантира, че картата остава визуално балансирана при произволен размер на шрифта.
Пример: икона с поддръжка на Dynamic Type. Размерът на иконите Image(systemName:) по подразбиране не се мащабира под Dynamic Type. @ScaledMetric решава този проблем: чрез промяна на imageScale или размер на frame въз основа на текущия коефициент на мащабиране.
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 wrapper-а, които проследяват промени, но с различни източници на обновяване. @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 за базовата мащабирана стойност и @State за множителя на анимацията. Крайната стойност = scaledValue × animationMultiplier.
Грешка 1: използване на @ScaledMetric за шрифтове. @ScaledMetric мащабира числа, а не шрифтове. За шрифтове използвайте .font(.body) — SwiftUI автоматично прилага Dynamic Type. Никога не използвайте @ScaledMetric с font(.system(size: scaledSize)) — това разрушава системната достъпност.
Грешка 2: липса на relativeTo за разнородни елементи. Ако имате padding (свързан с .body) и iconSize (свързан с .title), посочете правилния relativeTo за всеки. Без relativeTo и двата ще се мащабират според .body, което ще доведе до непропорционално увеличаване на иконата спрямо текстовия ѝ контекст.
Грешка 3: @ScaledMetric в ViewModel/@ObservableObject. @ScaledMetric е SwiftUI property wrapper, който работи само вътре в View. Не може да се използва в ViewModel или услуги. За мащабиране в ViewModel предайте мащабираната стойност от 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("Персонализирано мащабирано съдържание")
.font(.body)
.padding(basePadding + extraPadding)
}
}
Допълнително разстояние extraPadding се добавя само при размери за достъпност, давайки повече пространство за голям текст без промяна на основната @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 без размери за достъпност. На tvOS Dynamic Type липсва — @ScaledMetric винаги връща базовата стойност.
Да, за тестване на @ScaledMetric създайте View с @ScaledMetric и предайте environment стойността .sizeCategory чрез .environment(\.sizeCategory, .extraExtraLarge). След това вземете размера на елемента чрез GeometryReader или SwiftUI Inspector. Алтернативно, проверете логиката на мащабиране чрез UIFontMetrics в отделен модул.
.dynamicTypeSize — модификатор на View, който ограничава максималния Dynamic Type за йерархия (напр. .dynamicTypeSize(...large)). @ScaledMetric взема предвид това ограничение: ако .dynamicTypeSize е зададен, мащабираната стойност няма да надвиши съответния размер. Комбинирайте двата API за прецизен контрол.
Уверете се, че View използва @ScaledMetric вътре в себе си (не в ViewModel). Проверете дали View се абонира за Dynamic Type: @ScaledMetric автоматично задейства обновяване на body, но ако View използва .equatable() или .id(), механизмът може да се счупи. Използвайте @Environment(\.sizeCategory) като резервен вариант.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също