@ScaledMetric — qué es, property wrapper de SwiftUI y Dynamic Type

Autor: IT Sectr Publicado: 2026-06-27 Tiempo de lectura: 10 min

@ScaledMetric es un property wrapper de SwiftUI que escala automáticamente un valor numérico según la configuración de Dynamic Type del usuario. El valor se envuelve en @ScaledMetric y se recalcula al cambiar el tamaño de fuente del sistema, garantizando la accesibilidad de la interfaz para personas con discapacidad visual. Según Apple Developer Documentation (2026), @ScaledMetric utiliza la escala UIFontMetrics para calcular la escala relativa basada en la preferred content size category. Más información sobre accesibilidad en el artículo de accesibilidad de SwiftUI.

Puntos clave

  • @ScaledMetric — un property wrapper de SwiftUI para escalar valores según Dynamic Type.
  • Dynamic Type — una configuración del sistema iOS que cambia el tamaño de fuente desde UIFontTextStyle.
  • Escalado — @ScaledMetric toma un valor base y un multiplicador relativeTo.
  • Actualización automática — al cambiar Dynamic Type, @ScaledMetric recalcula y la UI se actualiza.
  • Accesibilidad — usar @ScaledMetric mejora la accesibilidad de la interfaz sin código adicional.

¿Qué es @ScaledMetric?

@ScaledMetric es un property wrapper de SwiftUI, añadido en iOS 14, que escala automáticamente un valor numérico (CGFloat, Int, Double) al tamaño de fuente Dynamic Type actual. A diferencia de .font(.body) para fuentes, @ScaledMetric escala cualquier parámetro numérico: padding, spacing, cornerRadius, iconSize — todo lo que debe aumentar proporcionalmente con texto más grande.

El propósito principal de @ScaledMetric es proporcionar escalado de accesibilidad para elementos de interfaz no textuales. Cuando el usuario aumenta el tamaño de fuente en la configuración de iOS, los botones, iconos y espacios deben escalarse proporcionalmente para mantener la interfaz equilibrada. @ScaledMetric maneja esto automáticamente, sin cálculo manual de multiplicadores.

Sintaxis de @ScaledMetric

Sintaxis básica de @ScaledMetric usa un valor predeterminado y un parámetro opcional relativeTo. Si se especifica relativeTo, el escalado se vincula a un estilo de texto específico (UIFontTextStyle). Si no, se usa la escala de .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 se escalará respecto a .body (predeterminado), iconSize respecto a .title. Con texto más grande, los espacios y los iconos aumentarán proporcionalmente. Sin @ScaledMetric, el padding permanecería en 12 pt independientemente del tamaño de fuente, causando desequilibrio visual.

Cómo funciona @ScaledMetric

El mecanismo de @ScaledMetric se basa en UIFontMetrics de UIKit. Cuando SwiftUI crea una instancia de @ScaledMetric, calcula un multiplicador basado en la preferred content size category actual (UIContentSizeCategory). El valor base se multiplica por scaledValue de UIFontMetrics para el estilo de texto especificado.

Matemáticamente: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Si no se especifica relativeTo, se usa UIFontMetrics.default, vinculado a .body. Cuando Dynamic Type cambia, SwiftUI recrea el cuerpo de la View, @ScaledMetric calcula un nuevo scaledValue y la UI se actualiza automáticamente mediante el mecanismo de PropertyWrappers similar a @State.

Escala de Dynamic Type

La escala de iOS incluye 11 tamaños: desde .extraSmall (5 pt) hasta .accessibilityExtraExtraExtraLarge (77 pt para .body). El factor de escalado para .body varía de 0.85 (XS) a 1.71 (XXXL) respecto al valor base. @ScaledMetric usa exactamente esta escala, por lo que un valor de padding de 12 pt podría convertirse en ~20 pt en el tamaño de accesibilidad máximo.

Categoría de tamaño de contenidoFactor (body)Ejemplo @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

Elegir relativeTo: use .body para valores relacionados con texto del cuerpo (padding, spacing en listas), .title para elementos grandes (iconSize, imageSize), .caption para elementos pequeños (tamaño de badge). Esto garantiza que los elementos escalen al mismo ritmo que el texto circundante.

@ScaledMetric y Dynamic Type

Dynamic Type es una función de iOS que permite a los usuarios ajustar el tamaño de fuente del sistema en Ajustes → Pantalla y brillo → Tamaño del texto. El cambio se aplica globalmente a todas las aplicaciones. @ScaledMetric reacciona a este cambio automáticamente: SwiftUI actualiza todas las variables @ScaledMetric cuando cambia UIContentSizeCategory.

Importante: @ScaledMetric solo escala valores numéricos, no gestiona fuentes directamente. Para fuentes, use .font() con un estilo de texto (.body, .title, .headline) — SwiftUI escala la fuente automáticamente. @ScaledMetric complementa el escalado de fuentes para padding, spacing y tamaños de elementos.

Pruebas de accesibilidad mediante Canvas

Canvas Preview admite Dynamic Type: la barra de herramientas de Canvas tiene un control deslizante de tamaño de texto (A–A) para probar la interfaz con diferentes tamaños de fuente. Úselo con @ScaledMetric para verificar que los espacios y tamaños escalen correctamente.

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 escala de 16 pt a ~27 pt en el tamaño de accesibilidad máximo. spacing — de 8 a ~14 pt. Esto garantiza que la tarjeta permanezca visualmente equilibrada en cualquier tamaño de fuente.

Ejemplos de @ScaledMetric

Ejemplo: icono compatible con Dynamic Type. Los tamaños de icono de Image(systemName:) no escalan con Dynamic Type de forma predeterminada. @ScaledMetric resuelve este problema cambiando el imageScale o el tamaño del frame según el factor de escala actual.

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

Ejemplo: componente badge accesible. Un badge numerado debe escalarse proporcionalmente al texto. @ScaledMetric para el tamaño mínimo del badge garantiza que el badge circular permanezca visible con texto 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 escala adicionalmente el contenido del Circle para coincidir con el badgeSize agrandado. Sin fontScale, el texto dentro del badge podría no caber con texto grande.

@ScaledMetric vs @State — diferencias

@ScaledMetric y @State son ambos property wrappers que rastrean cambios, pero con diferentes fuentes de actualización. @State actualiza el valor ante un cambio programático (mediante $stateBinding). @ScaledMetric actualiza el valor automáticamente cuando cambia el Dynamic Type del sistema, pero no permite cambiar el valor directamente desde el código.

Diferencia clave: @ScaledMetric es de solo lectura para el desarrollador y solo escritura para el sistema. No puede cambiar scaledValue mediante un setter — SwiftUI lo calcula basándose en el valor base y el Dynamic Type actual. @State, por otro lado, está completamente controlado por el desarrollador. Si necesita un valor que tanto escale con Dynamic Type como cambie programáticamente — combine @ScaledMetric con @State o use una propiedad calculada.

Característica@ScaledMetric@State
Fuente de actualizaciónDynamic Type (sistema)Programática (desarrollador)
Tipo de valorCGFloat, Int, DoubleCualquiera
Cambio desde códigoNo permitidoPermitido mediante binding
Redibujado de ViewAl cambiar Dynamic TypeAl cambiar el valor
Versión de iOSiOS 14+iOS 13+

Patrón combinado: si necesita cambiar el padding programáticamente (por ejemplo, animación de toque) mientras escala con Dynamic Type, cree un @ScaledMetric para el valor escalado base y un @State para el multiplicador de animación. Valor final = scaledValue × animationMultiplier.

Errores comunes con @ScaledMetric

Error 1: Usar @ScaledMetric para fuentes. @ScaledMetric escala números, no fuentes. Para fuentes, use .font(.body) — SwiftUI aplica Dynamic Type automáticamente. Nunca use @ScaledMetric con font(.system(size: scaledSize)) — rompe la accesibilidad del sistema.

Error 2: Falta de relativeTo para elementos heterogéneos. Si tiene padding (vinculado a .body) e iconSize (vinculado a .title), especifique el relativeTo correcto para cada uno. Sin relativeTo, ambos escalarán según .body, dando un escalado desproporcionado del icono respecto a su contexto textual.

Error 3: @ScaledMetric en ViewModel/@ObservableObject. @ScaledMetric es un property wrapper de SwiftUI que solo funciona dentro de una View. No puede usarse en ViewModels o servicios. Para escalado en ViewModel, pase el valor escalado desde la View como parámetro o use @Environment(\.sizeCategory) en la View.

Obtener la categoría de tamaño actual en código

@Environment(\.sizeCategory) es una forma alternativa de obtener el Dynamic Type actual en una View. Úselo cuando necesite más control: calcular un multiplicador personalizado, pasar sizeCategory a un ViewModel o combinarlo con @ScaledMetric para un escalado 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 extra extraPadding se añade solo en tamaños de accesibilidad, dando más espacio para texto grande sin cambiar la lógica base de @ScaledMetric.

Preguntas frecuentes

¿En qué se diferencia @ScaledMetric de @ScaledFont?

@ScaledMetric es un property wrapper oficial de SwiftUI para escalar números. @ScaledFont no existe como API estándar — es un wrapper personalizado implementado por la comunidad. Para fuentes, use siempre .font() integrado con estilos de texto (.body, .title), y @ScaledMetric para padding, spacing y tamaños.

¿Funciona @ScaledMetric en watchOS y tvOS?

@ScaledMetric está disponible en iOS 14+, watchOS 7+, tvOS 14+ y macOS 11+. En watchOS, Dynamic Type está limitado a un rango menor — tamaños desde .extraSmall hasta .extraLarge sin tamaños de accesibilidad. En tvOS, Dynamic Type no está disponible — @ScaledMetric siempre devuelve el valor base.

¿Se puede probar @ScaledMetric en tests unitarios?

Sí, para probar @ScaledMetric cree una View con @ScaledMetric y pase el valor de entorno .sizeCategory mediante .environment(\.sizeCategory, .extraExtraLarge). Luego obtenga el tamaño del elemento mediante GeometryReader o SwiftUI Inspector. Alternativamente, pruebe la lógica de escalado mediante UIFontMetrics en un módulo separado.

¿Cómo interactúa @ScaledMetric con .dynamicTypeSize?

.dynamicTypeSize es un modificador de View que limita el Dynamic Type máximo para una jerarquía (por ejemplo, .dynamicTypeSize(...large)). @ScaledMetric respeta este límite: si .dynamicTypeSize está establecido, el valor escalado no superará el tamaño correspondiente. Combine ambas API para un control preciso.

¿Qué hacer si @ScaledMetric no actualiza la UI?

Asegúrese de que la View use @ScaledMetric internamente (no en un ViewModel). Verifique que la View se suscriba a Dynamic Type: @ScaledMetric activa automáticamente el refresco del body, pero si la View usa .equatable() o .id(), el mecanismo puede romperse. Use @Environment(\.sizeCategory) como fallback.

Resumen

  • @ScaledMetric — un property wrapper de SwiftUI para escalado automático de números a Dynamic Type.
  • Vinculación — relativeTo vincula la escala a un estilo de texto específico (body, title, caption).
  • Accesibilidad — @ScaledMetric mejora la accesibilidad de la interfaz sin código manual.
  • Solo números — el wrapper escala CGFloat, Int, Double, pero no fuentes.
  • Rango — de 0.85 (XS) a 1.71 (XXXL) respecto al valor base.
  • Solo lectura — @ScaledMetric no puede modificarse desde código, solo a través del sistema.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también