@ScaledMetric — property wrapper SwiftUI care scalează automat valoarea numerică conform setărilor Dynamic Type ale utilizatorului. Valoarea este înfășurată în @ScaledMetric și recalculată la modificarea dimensiunii sistemice a fontului, garantând accesibilitatea interfeței pentru persoanele cu deficiențe de vedere. Conform Apple Developer Documentation (2026), @ScaledMetric utilizează scala UIFontMetrics pentru a calcula scara relativă pe baza preferred content size category. Citiți mai multe despre accessibility în materialul despre accessibility SwiftUI.
Principalele puncte
@ScaledMetric — property wrapper SwiftUI, adăugat în iOS 14, care scalează automat valoarea numerică (CGFloat, Int, Double) la dimensiunea actuală a fontului Dynamic Type. Spre deosebire de .font(.body) pentru fonturi, @ScaledMetric scalează orice parametru numeric: padding, spacing, cornerRadius, iconSize — tot ce ar trebui să crească proporțional la textul mare.
Sarcina principală a @ScaledMetric este de a asigura scalarea accessibility a elementelor non-text ale interfeței. Când utilizatorul mărește fontul în setările iOS, butoanele, pictogramele și spațierile trebuie să crească proporțional pentru ca interfața să rămână echilibrată. @ScaledMetric rezolvă această sarcină automat, fără calcularea manuală a multiplicatorilor.
Sintaxa de bază @ScaledMetric utilizează valoarea implicită și parametrul opțional relativeTo. Dacă relativeTo este specificat, scalarea este legată de un stil textual specific (UIFontTextStyle). Dacă nu — se utilizează scala .body.
struct AccessibleButton: View {
@ScaledMetric private var padding: CGFloat = 12
@ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = 24
var body: some View {
Label("Trimiteți", systemImage: "checkmark.circle.fill")
.font(.body)
.padding(padding)
.imageScale(.init(rawValue: iconSize / 24) ?? .medium)
}
}
padding va fi scalat relativ la .body (implicit), iconSize — relativ la .title. La text mare, spațierile și pictograma vor crește proporțional. Fără @ScaledMetric, spațierile ar rămâne 12 pt la orice dimensiune a fontului, ceea ce ar duce la dezechilibru vizual.
Mecanismul @ScaledMetric se bazează pe UIFontMetrics din UIKit. Când SwiftUI creează o instanță @ScaledMetric, calculează multiplicatorul pe baza preferred content size category curente (UIContentSizeCategory). Valoarea de bază este înmulțită cu scaledValue din UIFontMetrics pentru stilul textual specificat.
Matematic: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Dacă relativeTo nu este specificat, se utilizează UIFontMetrics.default legat la .body. La modificarea Dynamic Type, SwiftUI recreate corpul View, @ScaledMetric calculează noua scaledValue și UI se actualizează automat prin mecanismul PropertyWrappers asemănător @State.
Scala iOS include 11 dimensiuni: de la .extraSmall (5 pt) la .accessibilityExtraExtraExtraLarge (77 pt pentru .body). Coeficientul de scalare pentru .body variază de la 0.85 (XS) la 1.71 (XXXL) față de valoarea de bază. @ScaledMetric utilizează exact această scară, deci valoarea 12 pt pentru padding poate deveni ~20 pt la dimensiunea maximă de accessibility.
| Content Size Category | Coeficient (body) | Exemplu @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 |
Alegerea relativeTo: utilizați .body pentru valori legate de textul principal (padding, spacing în liste), .title pentru elemente mari (iconSize, imageSize), .caption pentru elemente mici (dimensiune badge). Aceasta garantează că elementele se scalează în armonie cu textul înconjurător.
Dynamic Type — funcție iOS care permite utilizatorului să ajusteze dimensiunea fontului sistemului în Settings → Display & Brightness → Text Size. Modificarea se aplică global tuturor aplicațiilor. @ScaledMetric reacționează automat la această modificare: SwiftUI actualizează toate variabilele @ScaledMetric la schimbarea UIContentSizeCategory.
Important: @ScaledMetric scalează doar valorile numerice, dar nu gestionează direct fonturile. Pentru fonturi utilizați .font() cu stil textual (.body, .title, .headline) — SwiftUI scalează automat fontul. @ScaledMetric completează scalarea fonturilor pentru padding, spacing și dimensiuni ale elementelor.
Canvas Preview suportă Dynamic Type: în panoul de instrumente Canvas există un glisor Text Size (A–A) pentru verificarea UI la diferite dimensiuni ale fontului. Utilizați-l cu @ScaledMetric pentru a vă asigura că spațierile și dimensiunile se scalează corect.
struct CardView: View {
@ScaledMetric private var cornerRadius: CGFloat = 16
@ScaledMetric private var spacing: CGFloat = 8
var body: some View {
VStack(spacing: spacing) {
Text("Titlu card")
.font(.headline)
Text("Descriere cu suport 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 se scalează de la 16 pt la ~27 pt la dimensiunea maximă de accessibility. spacing — de la 8 la ~14 pt. Aceasta garantează că cardul rămâne vizual echilibrat la orice dimensiune a fontului.
Exemplu: pictogramă cu suport Dynamic Type. Dimensiunea pictogramelor Image(systemName:) în mod implicit nu se scalează sub Dynamic Type. @ScaledMetric rezolvă această problemă: modificând imageScale sau dimensiunea frame pe baza factorului de scară curent.
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)
}
}
}
Exemplu: component badge accesibil. Badge cu număr trebuie să se scaleze proporțional cu textul. @ScaledMetric pentru dimensiunea minimă a badge-ului garantează că badge-ul circular rămâne vizibil la font mare.
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 scalează suplimentar conținutul Circle pentru a se potrivi cu badgeSize mărit. Fără fontScale, textul din interiorul badge-ului ar putea să nu încapă la font mare.
@ScaledMetric și @State — ambii property wrappers care urmăresc modificările, dar cu surse de actualizare diferite. @State actualizează valoarea la modificarea programatică (prin $stateBinding). @ScaledMetric actualizează valoarea automat la modificarea Dynamic Type de sistem, dar nu permite modificarea directă a valorii din cod.
Diferența cheie: @ScaledMetric — read-only pentru dezvoltator și write-only pentru sistem. Nu puteți modifica scaledValue prin setter — este calculat de SwiftUI pe baza valorii de bază și a Dynamic Type curent. @State, dimpotrivă, este complet gestionat de dezvoltator. Dacă aveți nevoie de o valoare care atât se scalează sub Dynamic Type, cât și se modifică programatic — combinați @ScaledMetric cu @State sau utilizați o proprietate calculată.
| Caracteristică | @ScaledMetric | @State |
|---|---|---|
| Sursa de actualizare | Dynamic Type (sistem) | Programatic (dezvoltator) |
| Tipul valorii | CGFloat, Int, Double | Orice |
| Modificare din cod | Nu se poate | Se poate prin binding |
| Re-redarea View | La modificarea Dynamic Type | La modificarea valorii |
| Versiune iOS | iOS 14+ | iOS 13+ |
Model combinat: dacă trebuie să modificați padding programatic (de exemplu, animație la apăsare) și simultan să scalați sub Dynamic Type, creați un @ScaledMetric pentru valoarea de bază scalată și un @State pentru multiplicatorul de animație. Valoarea finală = scaledValue × animationMultiplier.
Greșeala 1: utilizarea @ScaledMetric pentru fonturi. @ScaledMetric scalează numere, nu fonturi. Pentru fonturi utilizați .font(.body) — SwiftUI aplică automat Dynamic Type. Nu utilizați niciodată @ScaledMetric cu font(.system(size: scaledSize)) — aceasta strică accessibility-ul sistemului.
Greșeala 2: lipsa relativeTo pentru elemente eterogene. Dacă aveți padding (legat de .body) și iconSize (legat de .title), specificați relativeTo corect pentru fiecare. Fără relativeTo, ambele se vor scala după .body, ceea ce va duce la o creștere disproporționată a pictogramei față de contextul său textual.
Greșeala 3: @ScaledMetric în ViewModel/@ObservableObject. @ScaledMetric este un property wrapper SwiftUI care funcționează doar în interiorul View. Nu poate fi utilizat în ViewModel sau servicii. Pentru scalare în ViewModel, transmiteți valoarea scalată din View ca parametru sau utilizați @Environment(\.sizeCategory) în View.
@Environment(\.sizeCategory) — metodă alternativă de obținere a Dynamic Type curent în View. Utilizați-o când aveți nevoie de mai mult control: calcularea unui multiplicator personalizat, transmiterea sizeCategory la ViewModel sau combinarea cu @ScaledMetric pentru scalare flexibilă.
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("Conținut scalat personalizat")
.font(.body)
.padding(basePadding + extraPadding)
}
}
Spațiere suplimentară extraPadding se adaugă doar la dimensiunile de accessibility, oferind mai mult spațiu pentru textul mare fără a modifica logica de bază @ScaledMetric.
Întrebări frecvente
@ScaledMetric — property wrapper oficial SwiftUI pentru scalarea numerelor. @ScaledFont nu există ca API standard — este un wrapper personalizat implementat de comunitate. Pentru fonturi utilizați întotdeauna .font() încorporat cu stiluri textuale (.body, .title), iar @ScaledMetric — pentru padding, spacing și dimensiuni.
@ScaledMetric este disponibil pe iOS 14+, watchOS 7+, tvOS 14+ și macOS 11+. Pe watchOS, Dynamic Type este limitat la un interval mai mic — dimensiuni de la .extraSmall la .extraLarge fără dimensiuni de accessibility. Pe tvOS, Dynamic Type lipsește — @ScaledMetric returnează întotdeauna valoarea de bază.
Da, pentru testarea @ScaledMetric creați un View cu @ScaledMetric și transmiteți valoarea environment .sizeCategory prin .environment(\.sizeCategory, .extraExtraLarge). Apoi obțineți dimensiunea elementului prin GeometryReader sau SwiftUI Inspector. Alternativ, verificați logica de scalare prin UIFontMetrics într-un modul separat.
.dynamicTypeSize — un modifier View care limitează Dynamic Type maxim pentru o ierarhie (de exemplu, .dynamicTypeSize(...large)). @ScaledMetric ține cont de această limitare: dacă .dynamicTypeSize este setată, valoarea scalată nu va depăși dimensiunea corespunzătoare. Combinați ambele API-uri pentru un control precis.
Asigurați-vă că View utilizează @ScaledMetric în interiorul său (nu în ViewModel). Verificați că View se abonează la Dynamic Type: @ScaledMetric apelează automat reîmprospătarea body, dar dacă View utilizează .equatable() sau .id(), mecanismul se poate strica. Utilizați @Environment(\.sizeCategory) ca fallback.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și