@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 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 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.
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.
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.
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 contenido | Factor (body) | Ejemplo @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 |
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.
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.
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.
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.
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.
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.
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 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ón | Dynamic Type (sistema) | Programática (desarrollador) |
| Tipo de valor | CGFloat, Int, Double | Cualquiera |
| Cambio desde código | No permitido | Permitido mediante binding |
| Redibujado de View | Al cambiar Dynamic Type | Al cambiar el valor |
| Versión de iOS | iOS 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.
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.
@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.
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
@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.
@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.
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.
.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.
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
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.
Lea también