@ScaledMetric — qu’est-ce que c’est, property wrapper SwiftUI et Dynamic Type

Auteur : IT Sectr Publié le : 2026-06-27 Temps de lecture : 10 min

@ScaledMetric est un property wrapper SwiftUI qui met automatiquement à l’échelle une valeur numérique en fonction des paramètres Dynamic Type de l’utilisateur. La valeur est encapsulée dans @ScaledMetric et recalculée lorsque la taille de police système change, garantissant l’accessibilité de l’interface pour les personnes malvoyantes. Selon la documentation Apple Developer (2026), @ScaledMetric utilise l’échelle UIFontMetrics pour calculer l’échelle relative basée sur la catégorie de taille de contenu préférée. En savoir plus sur l’accessibilité dans l’article sur l’accessibilité SwiftUI.

Points clés

  • @ScaledMetric — un property wrapper SwiftUI pour mettre à l’échelle les valeurs selon Dynamic Type.
  • Dynamic Type — un paramètre système iOS qui modifie la taille de police depuis UIFontTextStyle.
  • Mise à l’échelle — @ScaledMetric prend une valeur de base et un multiplicateur relativeTo.
  • Mise à jour automatique — lorsque Dynamic Type change, @ScaledMetric recalcule et l’interface est mise à jour.
  • Accessibilité — utiliser @ScaledMetric améliore l’accessibilité de l’interface sans code supplémentaire.

Qu’est-ce que @ScaledMetric ?

@ScaledMetric est un property wrapper SwiftUI, ajouté dans iOS 14, qui met automatiquement à l’échelle une valeur numérique (CGFloat, Int, Double) par rapport à la taille de police Dynamic Type actuelle. Contrairement à .font(.body) pour les polices, @ScaledMetric met à l’échelle tous les paramètres numériques : padding, spacing, cornerRadius, iconSize — tout ce qui doit augmenter proportionnellement avec un texte plus grand.

L’objectif principal de @ScaledMetric est de fournir une mise à l’échelle d’accessibilité pour les éléments d’interface non textuels. Lorsque l’utilisateur augmente la taille de police dans les réglages iOS, les boutons, icônes et espacements doivent évoluer proportionnellement pour garder l’interface équilibrée. @ScaledMetric gère cela automatiquement, sans calcul manuel de multiplicateurs.

Syntaxe de @ScaledMetric

Syntaxe de base de @ScaledMetric utilise une valeur par défaut et un paramètre optionnel relativeTo. Si relativeTo est spécifié, la mise à l’échelle est liée à un style de texte spécifique (UIFontTextStyle). Sinon, l’échelle .body est utilisée.

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 sera mis à l’échelle par rapport à .body (par défaut), iconSize par rapport à .title. Avec un texte plus grand, les espacements et les icônes augmenteront proportionnellement. Sans @ScaledMetric, le padding resterait à 12 pt quelle que soit la taille de police, provoquant un déséquilibre visuel.

Comment fonctionne @ScaledMetric

Le mécanisme @ScaledMetric est basé sur UIFontMetrics d’UIKit. Lorsque SwiftUI crée une instance @ScaledMetric, il calcule un multiplicateur basé sur la catégorie de taille de contenu préférée actuelle (UIContentSizeCategory). La valeur de base est multipliée par scaledValue d’UIFontMetrics pour le style de texte spécifié.

Mathématiquement : ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Si relativeTo n’est pas spécifié, UIFontMetrics.default est utilisé, lié à .body. Lorsque Dynamic Type change, SwiftUI recrée le corps de la View, @ScaledMetric calcule un nouveau scaledValue et l’interface est mise à jour automatiquement via le mécanisme des PropertyWrappers similaire à @State.

Barème de mise à l’échelle Dynamic Type

L’échelle iOS comprend 11 tailles : de .extraSmall (5 pt) à .accessibilityExtraExtraExtraLarge (77 pt pour .body). Le facteur de mise à l’échelle pour .body varie de 0.85 (XS) à 1.71 (XXXL) par rapport à la valeur de base. @ScaledMetric utilise exactement cette échelle, donc une valeur de padding de 12 pt peut devenir ~20 pt en taille d’accessibilité maximale.

Catégorie de taille de contenuFacteur (body)Exemple @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

Choisir relativeTo : utilisez .body pour les valeurs liées au texte du corps (padding, spacing dans les listes), .title pour les grands éléments (iconSize, imageSize), .caption pour les petits éléments (taille du badge). Cela garantit que les éléments évoluent en synchronisation avec le texte environnant.

@ScaledMetric et Dynamic Type

Dynamic Type est une fonctionnalité iOS qui permet aux utilisateurs d’ajuster la taille de police système dans Réglages → Affichage et luminosité → Taille du texte. Le changement s’applique globalement à toutes les applications. @ScaledMetric réagit à ce changement automatiquement : SwiftUI met à jour toutes les variables @ScaledMetric lorsque UIContentSizeCategory change.

Important : @ScaledMetric met à l’échelle uniquement les valeurs numériques, il ne gère pas directement les polices. Pour les polices, utilisez .font() avec un style de texte (.body, .title, .headline) — SwiftUI met à l’échelle la police automatiquement. @ScaledMetric complète la mise à l’échelle des polices pour le padding, le spacing et les tailles d’éléments.

Test d’accessibilité via Canvas

Canvas Preview prend en charge Dynamic Type : la barre d’outils Canvas dispose d’un curseur de taille de texte (A–A) pour tester l’interface à différentes tailles de police. Utilisez-le avec @ScaledMetric pour vérifier que les espacements et les tailles se mettent à l’échelle correctement.

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 passe de 16 pt à ~27 pt en taille d’accessibilité maximale. spacing — de 8 à ~14 pt. Cela garantit que la carte reste visuellement équilibrée quelle que soit la taille de police.

Exemples @ScaledMetric

Exemple : icône avec support Dynamic Type. Les tailles d’icône Image(systemName:) ne sont pas mises à l’échelle pour Dynamic Type par défaut. @ScaledMetric résout ce problème en modifiant imageScale ou la taille du cadre en fonction du facteur d’échelle actuel.

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

Exemple : composant badge accessible. Un badge numéroté doit être mis à l’échelle proportionnellement au texte. @ScaledMetric pour la taille minimale du badge garantit que le badge circulaire reste visible avec du texte grand.

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 met supplémentairement à l’échelle le contenu du Circle pour correspondre à badgeSize agrandi. Sans fontScale, le texte à l’intérieur du badge pourrait ne pas tenir avec du texte grand.

@ScaledMetric vs @State — différences

@ScaledMetric et @State sont tous deux des property wrappers qui suivent les changements, mais avec des sources de mise à jour différentes. @State met à jour la valeur lors d’un changement programmatique (via $stateBinding). @ScaledMetric met à jour la valeur automatiquement lorsque le Dynamic Type système change, mais ne permet pas de modifier la valeur directement depuis le code.

Différence clé : @ScaledMetric est en lecture seule pour le développeur et en écriture seule pour le système. Vous ne pouvez pas modifier scaledValue via un setter — il est calculé par SwiftUI en fonction de la valeur de base et du Dynamic Type actuel. @State, en revanche, est entièrement contrôlé par le développeur. Si vous avez besoin d’une valeur qui à la fois s’adapte au Dynamic Type et change programmatiquement — combinez @ScaledMetric avec @State ou utilisez une propriété calculée.

Caractéristique@ScaledMetric@State
Source de mise à jourDynamic Type (système)Programmatique (développeur)
Type de valeurCGFloat, Int, DoubleN’importe lequel
Modification depuis le codeNon autoriséeAutorisée via binding
Re-dessin de la ViewAu changement de Dynamic TypeAu changement de valeur
Version iOSiOS 14+iOS 13+

Motif combiné : si vous devez modifier le padding programmatiquement (par exemple, animation de toucher) tout en l’adaptant au Dynamic Type, créez un @ScaledMetric pour la valeur de base mise à l’échelle et un @State pour le multiplicateur d’animation. Valeur finale = scaledValue × animationMultiplier.

Erreurs courantes avec @ScaledMetric

Erreur 1 : utiliser @ScaledMetric pour les polices. @ScaledMetric met à l’échelle les nombres, pas les polices. Pour les polices, utilisez .font(.body) — SwiftUI applique Dynamic Type automatiquement. N’utilisez jamais @ScaledMetric avec font(.system(size: scaledSize)) — cela casse l’accessibilité système.

Erreur 2 : absence de relativeTo pour des éléments hétérogènes. Si vous avez du padding (lié à .body) et iconSize (lié à .title), spécifiez le relativeTo correct pour chacun. Sans relativeTo, les deux seront mis à l’échelle par .body, donnant une mise à l’échelle disproportionnée de l’icône par rapport à son contexte textuel.

Erreur 3 : @ScaledMetric dans ViewModel/@ObservableObject. @ScaledMetric est un property wrapper SwiftUI qui ne fonctionne qu’à l’intérieur d’une View. Il ne peut pas être utilisé dans des ViewModels ou des services. Pour la mise à l’échelle dans ViewModel, passez la valeur mise à l’échelle depuis la View comme paramètre ou utilisez @Environment(\.sizeCategory) dans la View.

Obtenir la catégorie de taille actuelle dans le code

@Environment(\.sizeCategory) est une façon alternative d’obtenir le Dynamic Type actuel dans une View. Utilisez-le lorsque vous avez besoin de plus de contrôle : calculer un multiplicateur personnalisé, passer sizeCategory à un ViewModel ou le combiner avec @ScaledMetric pour une mise à l’échelle flexible.

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 supplémentaire extraPadding est ajouté uniquement aux tailles d’accessibilité, donnant plus d’espace pour le texte grand sans modifier la logique de base de @ScaledMetric.

Questions fréquentes

En quoi @ScaledMetric diffère-t-il de @ScaledFont ?

@ScaledMetric est un property wrapper officiel SwiftUI pour la mise à l’échelle des nombres. @ScaledFont n’existe pas en tant qu’API standard — c’est un wrapper personnalisé implémenté par la communauté. Pour les polices, utilisez toujours le .font() intégré avec les styles de texte (.body, .title) et @ScaledMetric pour le padding, le spacing et les tailles.

@ScaledMetric fonctionne-t-il sur watchOS et tvOS ?

@ScaledMetric est disponible sur iOS 14+, watchOS 7+, tvOS 14+ et macOS 11+. Sur watchOS, Dynamic Type est limité à une plage plus petite — les tailles de .extraSmall à .extraLarge sont disponibles sans tailles d’accessibilité. Sur tvOS, Dynamic Type n’est pas disponible — @ScaledMetric retourne toujours la valeur de base.

Peut-on tester @ScaledMetric dans des tests unitaires ?

Oui, pour tester @ScaledMetric créez une View avec @ScaledMetric et passez la valeur d’environnement .sizeCategory via .environment(\.sizeCategory, .extraExtraLarge). Obtenez ensuite la taille de l’élément via GeometryReader ou SwiftUI Inspector. Alternativement, testez la logique de mise à l’échelle via UIFontMetrics dans un module séparé.

Comment @ScaledMetric interagit-il avec .dynamicTypeSize ?

.dynamicTypeSize est un modificateur de View qui limite le Dynamic Type maximum pour une hiérarchie (par exemple, .dynamicTypeSize(...large)). @ScaledMetric respecte cette limite : si .dynamicTypeSize est défini, la valeur mise à l’échelle ne dépassera pas la taille correspondante. Combinez les deux API pour un contrôle précis.

Que faire si @ScaledMetric ne met pas à jour l’interface ?

Assurez-vous que la View utilise @ScaledMetric en interne (pas dans un ViewModel). Vérifiez que la View s’abonne à Dynamic Type : @ScaledMetric déclenche automatiquement l’actualisation du body, mais si la View utilise .equatable() ou .id(), le mécanisme peut se casser. Utilisez @Environment(\.sizeCategory) comme solution de repli.

Résumé

  • @ScaledMetric — un property wrapper SwiftUI pour la mise à l’échelle automatique des nombres vers Dynamic Type.
  • Liaison — relativeTo lie l’échelle à un style de texte spécifique (body, title, caption).
  • Accessibilité — @ScaledMetric améliore l’accessibilité de l’interface sans code manuel.
  • Nombres uniquement — le wrapper met à l’échelle CGFloat, Int, Double, mais pas les polices.
  • Plage — de 0.85 (XS) à 1.71 (XXXL) par rapport à la valeur de base.
  • Lecture seule — @ScaledMetric ne peut pas être modifié depuis le code, seulement via le système.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi