@ScaledMetric — cos’è, property wrapper SwiftUI e Dynamic Type

Autore: IT Sectr Pubblicato: 2026-06-27 Tempo di lettura: 10 min

@ScaledMetric è un property wrapper SwiftUI che ridimensiona automaticamente un valore numerico in base alle impostazioni Dynamic Type dell’utente. Il valore viene avvolto in @ScaledMetric e ricalcolato quando cambia la dimensione del carattere di sistema, garantendo l’accessibilità dell’interfaccia per le persone con disabilità visive. Secondo la Documentazione per sviluppatori Apple (2026), @ScaledMetric utilizza la scala UIFontMetrics per calcolare la scala relativa basata sulla categoria di dimensione del contenuto preferita. Scopri di più sull’accessibilità nell’articolo sull’accessibilità SwiftUI.

Punti chiave

  • @ScaledMetric — un property wrapper SwiftUI per ridimensionare i valori in base a Dynamic Type.
  • Dynamic Type — un’impostazione di sistema iOS che modifica la dimensione del carattere da UIFontTextStyle.
  • Ridimensionamento — @ScaledMetric prende un valore base e un moltiplicatore relativeTo.
  • Aggiornamento automatico — quando Dynamic Type cambia, @ScaledMetric ricalcola e l’interfaccia viene aggiornata.
  • Accessibilità — usare @ScaledMetric migliora l’accessibilità dell’interfaccia senza codice aggiuntivo.

Cos’è @ScaledMetric?

@ScaledMetric è un property wrapper SwiftUI, aggiunto in iOS 14, che ridimensiona automaticamente un valore numerico (CGFloat, Int, Double) in base alla dimensione del carattere Dynamic Type corrente. A differenza di .font(.body) per i font, @ScaledMetric ridimensiona qualsiasi parametro numerico: padding, spacing, cornerRadius, iconSize — tutto ciò che deve aumentare proporzionalmente con testo più grande.

Lo scopo principale di @ScaledMetric è fornire un ridimensionamento di accessibilità per gli elementi dell’interfaccia non testuali. Quando l’utente aumenta la dimensione del carattere nelle impostazioni iOS, pulsanti, icone e spaziature devono ridimensionarsi proporzionalmente per mantenere l’interfaccia equilibrata. @ScaledMetric gestisce tutto ciò automaticamente, senza calcolo manuale dei moltiplicatori.

Sintassi di @ScaledMetric

Sintassi di base di @ScaledMetric utilizza un valore predefinito e un parametro opzionale relativeTo. Se viene specificato relativeTo, il ridimensionamento è legato a uno specifico stile di testo (UIFontTextStyle). In caso contrario, viene utilizzata la scala .body.

swift
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 verrà ridimensionato rispetto a .body (predefinito), iconSize rispetto a .title. Con testo più grande, le spaziature e le icone aumenteranno proporzionalmente. Senza @ScaledMetric, il padding rimarrebbe a 12 pt indipendentemente dalla dimensione del carattere, causando squilibrio visivo.

Come funziona @ScaledMetric

Il meccanismo @ScaledMetric è basato su UIFontMetrics di UIKit. Quando SwiftUI crea un’istanza di @ScaledMetric, calcola un moltiplicatore basato sulla categoria di dimensione del contenuto preferita corrente (UIContentSizeCategory). Il valore base viene moltiplicato per scaledValue di UIFontMetrics per lo stile di testo specificato.

Matematicamente: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Se relativeTo non è specificato, viene utilizzato UIFontMetrics.default, legato a .body. Quando Dynamic Type cambia, SwiftUI ricrea il corpo della View, @ScaledMetric calcola un nuovo scaledValue e l’interfaccia viene aggiornata automaticamente attraverso il meccanismo dei PropertyWrappers simile a @State.

Scala di ridimensionamento Dynamic Type

La scala iOS include 11 dimensioni: da .extraSmall (5 pt) a .accessibilityExtraExtraExtraLarge (77 pt per .body). Il fattore di ridimensionamento per .body varia da 0.85 (XS) a 1.71 (XXXL) rispetto al valore base. @ScaledMetric utilizza esattamente questa scala, quindi un valore di padding di 12 pt può diventare ~20 pt alla dimensione di accessibilità massima.

Categoria dimensione contenutoFattore (body)Esempio @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

Scegliere relativeTo: utilizzare .body per i valori relativi al testo del corpo (padding, spacing negli elenchi), .title per elementi grandi (iconSize, imageSize), .caption per elementi piccoli (dimensione badge). Ciò garantisce che gli elementi si ridimensionino in sincronia con il testo circostante.

@ScaledMetric e Dynamic Type

Dynamic Type è una funzionalità iOS che consente agli utenti di regolare la dimensione del carattere di sistema in Impostazioni → Schermo e luminosità → Dimensione testo. La modifica si applica globalmente a tutte le app. @ScaledMetric reagisce a questa modifica automaticamente: SwiftUI aggiorna tutte le variabili @ScaledMetric quando UIContentSizeCategory cambia.

Importante: @ScaledMetric ridimensiona solo valori numerici, non gestisce direttamente i font. Per i font, utilizzare .font() con uno stile di testo (.body, .title, .headline) — SwiftUI ridimensiona automaticamente il font. @ScaledMetric completa il ridimensionamento dei font per padding, spacing e dimensioni degli elementi.

Test di accessibilità tramite Canvas

Canvas Preview supporta Dynamic Type: la barra degli strumenti di Canvas ha un cursore della dimensione del testo (A–A) per testare l’interfaccia a diverse dimensioni del carattere. Usalo con @ScaledMetric per verificare che spaziature e dimensioni si ridimensionino correttamente.

swift
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 si ridimensiona da 16 pt a ~27 pt alla massima dimensione di accessibilità. spacing — da 8 a ~14 pt. Ciò garantisce che la carta rimanga visivamente equilibrata a qualsiasi dimensione del carattere.

Esempi di @ScaledMetric

Esempio: icona con supporto Dynamic Type. Le dimensioni delle icone Image(systemName:) non vengono ridimensionate per Dynamic Type per impostazione predefinita. @ScaledMetric risolve questo problema modificando imageScale o la dimensione del frame in base al fattore di scala corrente.

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)
        }
    }
}

Esempio: componente badge accessibile. Un badge numerato deve ridimensionarsi proporzionalmente al testo. @ScaledMetric per la dimensione minima del badge garantisce che il badge circolare rimanga visibile con testo grande.

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 ridimensiona ulteriormente il contenuto del Circle per corrispondere a badgeSize ingrandito. Senza fontScale, il testo all’interno del badge potrebbe non entrare con testo grande.

@ScaledMetric vs @State — differenze

@ScaledMetric e @State sono entrambi property wrapper che tracciano i cambiamenti, ma con diverse fonti di aggiornamento. @State aggiorna il valore al cambiamento programmatico (tramite $stateBinding). @ScaledMetric aggiorna il valore automaticamente quando il Dynamic Type di sistema cambia, ma non consente di modificare il valore direttamente dal codice.

Differenza chiave: @ScaledMetric è di sola lettura per lo sviluppatore e di sola scrittura per il sistema. Non è possibile modificare scaledValue tramite un setter — viene calcolato da SwiftUI in base al valore base e al Dynamic Type corrente. @State, d’altra parte, è completamente controllato dallo sviluppatore. Se hai bisogno di un valore che sia ridimensionato per Dynamic Type e cambi anche programmaticamente — combina @ScaledMetric con @State o utilizza una proprietà calcolata.

Caratteristica@ScaledMetric@State
Fonte di aggiornamentoDynamic Type (sistema)Programmatica (sviluppatore)
Tipo di valoreCGFloat, Int, DoubleQualsiasi
Modifica dal codiceNon consentitaConsentita tramite binding
Ridisegno della ViewAl cambiamento di Dynamic TypeAl cambiamento del valore
Versione iOSiOS 14+iOS 13+

Pattern combinato: se devi modificare il padding programmaticamente (ad esempio, animazione di tocco) mentre lo ridimensioni per Dynamic Type, crea un @ScaledMetric per il valore ridimensionato base e un @State per il moltiplicatore di animazione. Valore finale = scaledValue × animationMultiplier.

Errori comuni con @ScaledMetric

Errore 1: Usare @ScaledMetric per i font. @ScaledMetric ridimensiona numeri, non font. Per i font, utilizzare .font(.body) — SwiftUI applica Dynamic Type automaticamente. Non usare mai @ScaledMetric con font(.system(size: scaledSize)) — rompe l’accessibilità del sistema.

Errore 2: Mancanza di relativeTo per elementi eterogenei. Se hai padding (legato a .body) e iconSize (legato a .title), specifica il relativeTo corretto per ciascuno. Senza relativeTo, entrambi si ridimensioneranno secondo .body, dando un ridimensionamento sproporzionato dell’icona rispetto al suo contesto testuale.

Errore 3: @ScaledMetric in ViewModel/@ObservableObject. @ScaledMetric è un property wrapper SwiftUI che funziona solo all’interno di una View. Non può essere utilizzato in ViewModel o servizi. Per il ridimensionamento in ViewModel, passa il valore ridimensionato dalla View come parametro o utilizza @Environment(\.sizeCategory) nella View.

Ottenere la categoria di dimensione corrente nel codice

@Environment(\.sizeCategory) è un modo alternativo per ottenere il Dynamic Type corrente in una View. Usalo quando hai bisogno di più controllo: calcolare un moltiplicatore personalizzato, passare sizeCategory a un ViewModel o combinarlo con @ScaledMetric per un ridimensionamento flessibile.

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("Custom scaled content")
            .font(.body)
            .padding(basePadding + extraPadding)
    }
}

Padding extra extraPadding viene aggiunto solo alle dimensioni di accessibilità, dando più spazio per il testo grande senza modificare la logica di base di @ScaledMetric.

Domande frequenti

In cosa @ScaledMetric è diverso da @ScaledFont?

@ScaledMetric è un property wrapper ufficiale SwiftUI per ridimensionare numeri. @ScaledFont non esiste come API standard — è un wrapper personalizzato implementato dalla comunità. Per i font, usa sempre il .font() integrato con stili di testo (.body, .title) e @ScaledMetric per padding, spacing e dimensioni.

@ScaledMetric funziona su watchOS e tvOS?

@ScaledMetric è disponibile su iOS 14+, watchOS 7+, tvOS 14+ e macOS 11+. Su watchOS, Dynamic Type è limitato a un intervallo più piccolo — dimensioni da .extraSmall a .extraLarge sono disponibili senza dimensioni di accessibilità. Su tvOS, Dynamic Type non è disponibile — @ScaledMetric restituisce sempre il valore base.

Si può testare @ScaledMetric nei test unitari?

Sì, per testare @ScaledMetric crea una View con @ScaledMetric e passa il valore ambientale .sizeCategory tramite .environment(\.sizeCategory, .extraExtraLarge). Quindi ottieni la dimensione dell’elemento tramite GeometryReader o SwiftUI Inspector. In alternativa, testa la logica di ridimensionamento tramite UIFontMetrics in un modulo separato.

Come interagisce @ScaledMetric con .dynamicTypeSize?

.dynamicTypeSize è un modificatore di View che limita il Dynamic Type massimo per una gerarchia (ad esempio, .dynamicTypeSize(...large)). @ScaledMetric rispetta questo limite: se .dynamicTypeSize è impostato, il valore ridimensionato non supererà la dimensione corrispondente. Combina entrambe le API per un controllo preciso.

Cosa fare se @ScaledMetric non aggiorna l’interfaccia?

Assicurati che la View utilizzi @ScaledMetric internamente (non in un ViewModel). Verifica che la View sia sottoscritta a Dynamic Type: @ScaledMetric attiva automaticamente l’aggiornamento del body, ma se la View utilizza .equatable() o .id(), il meccanismo potrebbe rompersi. Utilizza @Environment(\.sizeCategory) come fallback.

Riepilogo

  • @ScaledMetric — un property wrapper SwiftUI per il ridimensionamento automatico dei numeri a Dynamic Type.
  • Legame — relativeTo lega la scala a uno specifico stile di testo (body, title, caption).
  • Accessibilità — @ScaledMetric migliora l’accessibilità dell’interfaccia senza codice manuale.
  • Solo numeri — il wrapper ridimensiona CGFloat, Int, Double, ma non i font.
  • Intervallo — da 0.85 (XS) a 1.71 (XXXL) rispetto al valore base.
  • Sola lettura — @ScaledMetric non può essere modificato dal codice, solo tramite il sistema.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche