@ScaledMetric — шта је то, property wrapper SwiftUI и Dynamic Type

Аутор: IT Sectr Објављено: 2026-06-27 Време читања: 10 мин

@ScaledMetric — property wrapper SwiftUI који аутоматски скалира нумеричку вредност у складу са подешавањима Dynamic Type корисника. Вредност се обавија у @ScaledMetric и прерачунава при промени системске величине фонта, што гарантује приступачност интерфејса за особе са оштећењем вида. Према Apple Developer Documentation (2026), @ScaledMetric користи скалу UIFontMetrics за израчунавање релативне скале на основу preferred content size category. Више о accessibility прочитајте у материјалу о accessibility SwiftUI.

Главно

  • @ScaledMetric — property wrapper SwiftUI за скалирање вредности под Dynamic Type.
  • Dynamic Type — системско подешавање iOS-а које мења величину фонта из UIFontTextStyle.
  • Скалирање — @ScaledMetric прихвата основну вредност и множилац relativeTo.
  • Аутоматско ажурирање — при промени Dynamic Type, @ScaledMetric се прерачунава и UI се ажурира.
  • Accessibility — коришћење @ScaledMetric побољшава приступачност интерфејса без додатног кода.

Шта је @ScaledMetric?

@ScaledMetric — property wrapper SwiftUI, додат у iOS 14, који аутоматски скалира нумеричку вредност (CGFloat, Int, Double) под тренутну величину фонта Dynamic Type. За разлику од .font(.body) за фонтове, @ScaledMetric скалира било које нумеричке параметре: padding, spacing, cornerRadius, iconSize — све што треба пропорционално да се повећа при великом тексту.

Основни задатак @ScaledMetric је да обезбеди accessibility-скалирање не-текстуалних елемената интерфејса. Када корисник повећа фонт у подешавањима iOS-а, дугмићи, иконе и размаци треба да се повећају пропорционално како би интерфејс остао уравнотежен. @ScaledMetric решава овај задатак аутоматски, без ручног израчунавања множилаца.

Синтакса @ScaledMetric

Основна синтакса @ScaledMetric користи подразумевану вредност и опциони параметар relativeTo. Ако је relativeTo наведен, скалирање је везано за конкретан текстуални стил (UIFontTextStyle). Ако није — користи се скала .body.

swift
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

Механизам @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.

Скала скалирања Dynamic Type

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)
extraSmall0.85~10 pt
small0.93~11 pt
medium (default)1.0012 pt
large1.07~13 pt
extraLarge1.15~14 pt
extraExtraLarge1.28~15 pt
accessibilityExtraLarge1.47~18 pt
accessibilityXXXL1.71~20 pt

Избор relativeTo: користите .body за вредности везане за главни текст (padding, spacing у листама), .title за велике елементе (iconSize, imageSize), .caption за мале елементе (величина badge-а). Ово гарантује да се елементи скалирају у складу са околним текстом.

@ScaledMetric и Dynamic Type

Dynamic Type — iOS функција која омогућава кориснику да подеси величину системског фонта у Settings → Display & Brightness → Text Size. Промена се примењује глобално на све апликације. @ScaledMetric реагује на ову промену аутоматски: SwiftUI ажурира све @ScaledMetric променљиве при промени UIContentSizeCategory.

Важно: @ScaledMetric скалира само нумеричке вредности, али не управља директно фонтовима. За фонтове користите .font() са текстуалним стилом (.body, .title, .headline) — SwiftUI аутоматски скалира фонт. @ScaledMetric допуњује скалирање фонта за padding, spacing и величине елемената.

Провера accessibility кроз Canvas

Canvas Preview подржава Dynamic Type: на панелу алата Canvas налази се клизач Text Size (A–A) за проверу UI при различитим величинама фонта. Користите га са @ScaledMetric да бисте се уверили да се размаци и величине правилно скалирају.

swift
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 при максималној accessibility величини. spacing — од 8 до ~14 pt. Ово гарантује да картица остаје визуелно уравнотежена при било којој величини фонта.

Примери @ScaledMetric

Пример: икона са подршком за Dynamic Type. Величина икона Image(systemName:) подразумевано не скалира под Dynamic Type. @ScaledMetric решава овај проблем: мењајући imageScale или frame величину на основу тренутног фактора скале.

swift
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 остаје видљив при великом фонту.

swift
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 vs @State — разлике

@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.

Типичне грешке са @ScaledMetric

Грешка 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-у, проследите скалирану вредност из View-а као параметар или користите @Environment(\.sizeCategory) у View-у.

Добијање тренутне sizeCategory у коду

@Environment(\.sizeCategory) — алтернативни начин добијања тренутног Dynamic Type у View-у. Користите га када вам треба више контроле: израчунавање прилагођеног множиоца, прослеђивање sizeCategory у ViewModel или комбиновање са @ScaledMetric за флексибилно скалирање.

swift
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 се додаје само при accessibility величинама, дајући више простора за велики текст без промене основне @ScaledMetric логике.

Често постављана питања

По чему се @ScaledMetric разликује од @ScaledFont?

@ScaledMetric — званични property wrapper SwiftUI за скалирање бројева. @ScaledFont не постоји као стандардни API — то је прилагођени wrapper који је имплементирала заједница. За фонтове увек користите уграђени .font() са текстуалним стиловима (.body, .title), а @ScaledMetric — за padding, spacing и величине.

Да ли @ScaledMetric ради на watchOS и tvOS?

@ScaledMetric је доступан на iOS 14+, watchOS 7+, tvOS 14+ и macOS 11+. На watchOS је Dynamic Type ограничен на мањи опсег — доступне су величине од .extraSmall до .extraLarge без accessibility величина. На tvOS Dynamic Type не постоји — @ScaledMetric увек враћа основну вредност.

Може ли се @ScaledMetric тестирати у unit-тестовима?

Да, за тестирање @ScaledMetric креирајте View са @ScaledMetric и проследите environment вредност .sizeCategory кроз .environment(\.sizeCategory, .extraExtraLarge). Затим добијте величину елемента кроз GeometryReader или SwiftUI Inspector. Алтернативно, проверавајте логику скалирања кроз UIFontMetrics у посебном модулу.

Како @ScaledMetric интерагује са .dynamicTypeSize?

.dynamicTypeSize — View модификатор који ограничава максимални Dynamic Type за хијерархију (нпр. .dynamicTypeSize(...large)). @ScaledMetric узима у обзир ово ограничење: ако је .dynamicTypeSize постављен, скалирана вредност неће премашити одговарајућу величину. Комбинујте оба API-ја за прецизну контролу.

Шта радити ако @ScaledMetric не ажурира UI?

Уверите се да View користи @ScaledMetric унутар себе (не у ViewModel-у). Проверите да View претплаћује на Dynamic Type: @ScaledMetric аутоматски позива освежавање body-ја, али ако View користи .equatable() или .id(), механизам може да се поквари. Користите @Environment(\.sizeCategory) као fallback.

Резиме

  • @ScaledMetric — property wrapper SwiftUI за аутоматско скалирање бројева под Dynamic Type.
  • Везивање — relativeTo везује скалу за конкретан текстуални стил (body, title, caption).
  • Accessibility — @ScaledMetric побољшава приступачност интерфејса без ручног кода.
  • Само бројеви — wrapper скалира CGFloat, Int, Double, али не фонтове.
  • Опсег — од 0.85 (XS) до 1.71 (XXXL) у односу на основну вредност.
  • Read-only — @ScaledMetric се не може мењати из кода, само кроз систем.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође