@ScaledMetric — property wrapper SwiftUI, który automatycznie skaluje wartość liczbową zgodnie z ustawieniami Dynamic Type użytkownika. Wartość jest opakowywana w @ScaledMetric i przeliczana przy zmianie systemowego rozmiaru czcionki, co gwarantuje dostępność interfejsu dla osób z wadami wzroku. Według Apple Developer Documentation (2026), @ScaledMetric używa skali UIFontMetrics do obliczenia względnej skali na podstawie preferred content size category. Więcej o accessibility przeczytasz w materiale o accessibility SwiftUI.
Najważniejsze
@ScaledMetric — property wrapper SwiftUI, dodany w iOS 14, który automatycznie skaluje wartość liczbową (CGFloat, Int, Double) do bieżącego rozmiaru czcionki Dynamic Type. W przeciwieństwie do .font(.body) dla czcionek, @ScaledMetric skaluje dowolne parametry liczbowe: padding, spacing, cornerRadius, iconSize — wszystko, co powinno proporcjonalnie zwiększać się przy dużym tekście.
Głównym zadaniem @ScaledMetric jest zapewnienie accessibility-skalowania nie-tekstowych elementów interfejsu. Gdy użytkownik zwiększa czcionkę w ustawieniach iOS, przyciski, ikony i odstępy powinny zwiększać się proporcjonalnie, aby interfejs pozostał zbalansowany. @ScaledMetric rozwiązuje to zadanie automatycznie, bez ręcznego obliczania mnożników.
Podstawowa składnia @ScaledMetric używa wartości domyślnej i opcjonalnego parametru relativeTo. Jeśli relativeTo jest określony, skalowanie jest powiązane z konkretnym stylem tekstu (UIFontTextStyle). Jeśli nie — używana jest skala .body.
struct AccessibleButton: View {
@ScaledMetric private var padding: CGFloat = 12
@ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = 24
var body: some View {
Label("Prześlij", systemImage: "checkmark.circle.fill")
.font(.body)
.padding(padding)
.imageScale(.init(rawValue: iconSize / 24) ?? .medium)
}
}
padding będzie skalowany względem .body (domyślnie), iconSize — względem .title. Przy dużym tekście odstępy i ikona zwiększą się proporcjonalnie. Bez @ScaledMetric odstępy pozostałyby 12 pt przy dowolnym rozmiarze czcionki, co prowadziłoby do wizualnej nierównowagi.
Mechanizm @ScaledMetric opiera się na UIFontMetrics z UIKit. Gdy SwiftUI tworzy instancję @ScaledMetric, oblicza mnożnik na podstawie bieżącej preferred content size category (UIContentSizeCategory). Wartość bazowa jest mnożona przez scaledValue z UIFontMetrics dla określonego stylu tekstu.
Matematycznie: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Jeśli relativeTo nie jest określone, używany jest UIFontMetrics.default powiązany z .body. Przy zmianie Dynamic Type SwiftUI odtwarza ciało View, @ScaledMetric oblicza nową scaledValue i UI aktualizuje się automatycznie przez mechanizm @State-podobnych PropertyWrappers.
Skala iOS obejmuje 11 rozmiarów: od .extraSmall (5 pt) do .accessibilityExtraExtraExtraLarge (77 pt dla .body). Współczynnik skalowania dla .body waha się od 0.85 (XS) do 1.71 (XXXL) względem wartości bazowej. @ScaledMetric używa właśnie tej skali, więc wartość 12 pt dla padding może stać się ~20 pt przy maksymalnym accessibility-rozmiarze.
| Content Size Category | Współczynnik (body) | Przykład @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 |
Wybór relativeTo: używaj .body dla wartości powiązanych z głównym tekstem (padding, spacing w listach), .title dla dużych elementów (iconSize, imageSize), .caption dla małych elementów (rozmiar badge). Gwarantuje to, że elementy skalują się zgodnie z otaczającym tekstem.
Dynamic Type — funkcja iOS, która pozwala użytkownikowi dostosować rozmiar czcionki systemowej w Settings → Display & Brightness → Text Size. Zmiana jest stosowana globalnie do wszystkich aplikacji. @ScaledMetric reaguje na tę zmianę automatycznie: SwiftUI aktualizuje wszystkie zmienne @ScaledMetric przy zmianie UIContentSizeCategory.
Ważne: @ScaledMetric skaluje tylko wartości liczbowe, ale nie zarządza bezpośrednio czcionkami. Do czcionek używaj .font() ze stylem tekstu (.body, .title, .headline) — SwiftUI automatycznie skaluje czcionkę. @ScaledMetric uzupełnia skalowanie czcionek dla padding, spacing i rozmiarów elementów.
Canvas Preview obsługuje Dynamic Type: w panelu narzędzi Canvas znajduje się suwak Text Size (A–A) do sprawdzania UI przy różnych rozmiarach czcionki. Używaj go z @ScaledMetric, aby upewnić się, że odstępy i rozmiary skalują się prawidłowo.
struct CardView: View {
@ScaledMetric private var cornerRadius: CGFloat = 16
@ScaledMetric private var spacing: CGFloat = 8
var body: some View {
VStack(spacing: spacing) {
Text("Tytuł karty")
.font(.headline)
Text("Opis z obsługą 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 skaluje się od 16 pt do ~27 pt przy maksymalnym accessibility-rozmiarze. spacing — od 8 do ~14 pt. Gwarantuje to, że karta pozostaje wizualnie zbalansowana przy dowolnym rozmiarze czcionki.
Przykład: ikona z obsługą Dynamic Type. Rozmiar ikon Image(systemName:) domyślnie nie skaluje się pod Dynamic Type. @ScaledMetric rozwiązuje ten problem: zmieniając imageScale lub frame rozmiar na podstawie 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)
}
}
}
Przykład: dostępny badge-komponent. Badge z liczbą powinien skalować się proporcjonalnie do tekstu. @ScaledMetric dla minimalnego rozmiaru badge gwarantuje, że okrągły badge pozostaje widoczny przy dużej czcionce.
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 dodatkowo skaluje zawartość Circle, aby dopasować się do zwiększonego badgeSize. Bez fontScale tekst wewnątrz badge mógłby się nie zmieścić przy dużej czcionce.
@ScaledMetric i @State — oba property wrappers śledzące zmiany, ale z różnymi źródłami aktualizacji. @State aktualizuje wartość przy programowej zmianie (przez $stateBinding). @ScaledMetric aktualizuje wartość automatycznie przy zmianie systemowego Dynamic Type, ale nie pozwala na bezpośrednią zmianę wartości z kodu.
Kluczowa różnica: @ScaledMetric — read-only dla programisty i write-only dla systemu. Nie możesz zmienić scaledValue przez setter — jest obliczany przez SwiftUI na podstawie wartości bazowej i bieżącego Dynamic Type. @State natomiast jest w pełni zarządzany przez programistę. Jeśli potrzebujesz wartości, która zarówno skaluje się pod Dynamic Type, jak i zmienia programowo — łącz @ScaledMetric z @State lub użyj właściwości obliczanej.
| Cecha | @ScaledMetric | @State |
|---|---|---|
| Źródło aktualizacji | Dynamic Type (system) | Programowo (programista) |
| Typ wartości | CGFloat, Int, Double | Dowolny |
| Zmiana z kodu | Nie można | Można przez binding |
| Ponowne rysowanie View | Przy zmianie Dynamic Type | Przy zmianie wartości |
| Wersja iOS | iOS 14+ | iOS 13+ |
Połączony wzorzec: jeśli potrzebujesz zmieniać padding programowo (np. animacja naciśnięcia) i jednocześnie skalować pod Dynamic Type, utwórz @ScaledMetric dla bazowej scaled-wartości i @State dla mnożnika animacji. Wartość końcowa = scaledValue × animationMultiplier.
Błąd 1: używanie @ScaledMetric dla czcionek. @ScaledMetric skaluje liczby, a nie czcionki. Do czcionek używaj .font(.body) — SwiftUI automatycznie stosuje Dynamic Type. Nigdy nie używaj @ScaledMetric z font(.system(size: scaledSize)) — to psuje systemowe accessibility.
Błąd 2: brak relativeTo dla różnorodnych elementów. Jeśli masz padding (powiązany z .body) i iconSize (powiązany z .title), określ poprawny relativeTo dla każdego. Bez relativeTo oba będą skalowane według .body, co da nieproporcjonalne zwiększenie ikony względem jej kontekstu tekstowego.
Błąd 3: @ScaledMetric w ViewModel/@ObservableObject. @ScaledMetric — property wrapper SwiftUI działający tylko wewnątrz View. Nie można go używać w ViewModel lub serwisach. Do skalowania w ViewModel przekazuj scaled-wartość z View jako parametr lub używaj @Environment(\.sizeCategory) w View.
@Environment(\.sizeCategory) — alternatywny sposób pobierania bieżącego Dynamic Type w View. Używaj go, gdy potrzebujesz większej kontroli: obliczyć niestandardowy mnożnik, przekazać sizeCategory do ViewModel lub połączyć z @ScaledMetric dla elastycznego skalowania.
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("Niestandardowa skalowana zawartość")
.font(.body)
.padding(basePadding + extraPadding)
}
}
Dodatkowy odstęp extraPadding jest dodawany tylko przy accessibility-rozmiarach, dając więcej powietrza dla dużego tekstu bez zmiany podstawowej logiki @ScaledMetric.
Często zadawane pytania
@ScaledMetric — oficjalny property wrapper SwiftUI do skalowania liczb. @ScaledFont nie istnieje jako standardowe API — to niestandardowy wrapper zaimplementowany przez społeczność. Do czcionek zawsze używaj wbudowanego .font() ze stylami tekstu (.body, .title), a @ScaledMetric — dla padding, spacing i rozmiarów.
@ScaledMetric jest dostępny na iOS 14+, watchOS 7+, tvOS 14+ i macOS 11+. Na watchOS Dynamic Type jest ograniczony do mniejszego zakresu — dostępne rozmiary od .extraSmall do .extraLarge bez accessibility-rozmiarów. Na tvOS Dynamic Type nie występuje — @ScaledMetric zawsze zwraca wartość bazową.
Tak, do testowania @ScaledMetric utwórz View z @ScaledMetric i przekaż environment-wartość .sizeCategory przez .environment(\.sizeCategory, .extraExtraLarge). Następnie pobierz rozmiar elementu przez GeometryReader lub SwiftUI Inspector. Alternatywnie sprawdzaj logikę skalowania przez UIFontMetrics w osobnym module.
.dynamicTypeSize — modyfikator View ograniczający maksymalny Dynamic Type dla hierarchii (np. .dynamicTypeSize(...large)). @ScaledMetric uwzględnia to ograniczenie: jeśli .dynamicTypeSize jest ustawiony, scaled-wartość nie przekroczy odpowiedniego rozmiaru. Łącz oba API dla precyzyjnej kontroli.
Upewnij się, że View używa @ScaledMetric wewnątrz siebie (nie w ViewModel). Sprawdź, czy View subskrybuje Dynamic Type: @ScaledMetric automatycznie wywołuje odświeżenie body, ale jeśli View używa .equatable() lub .id(), mechanizm może się zepsuć. Użyj @Environment(\.sizeCategory) jako fallback.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również