SF Pro: ejes variables, escalado óptico y el contrato de Dynamic Type
SF Pro, la fuente del sistema de Apple desde iOS 9 / OS X El Capitan en 2015, es una fuente variable con tres ejes (peso, ancho, tamaño óptico), una familia diseñada que incluye una sans (SF Pro), una variante redondeada (SF Pro Rounded), una monoespaciada (SF Mono), una variante compacta (SF Compact, usada en watchOS) y una compañera serif (New York)1. La fuente está diseñada en torno al contrato de Dynamic Type: los once estilos de texto de SwiftUI (.largeTitle, .title, .body, .callout, .footnote, etc.) usan SF Pro de manera predeterminada, y todos escalan automáticamente cuando el usuario cambia su tamaño de texto preferido en Configuración.
La mayoría de las apps usan uno o dos de esos once estilos, ignoran Dynamic Type más allá del comportamiento predeterminado y nunca tocan los ejes variables. El resultado es una tipografía que funciona pero no habla el vocabulario de la plataforma. Este artículo recorre la familia de fuentes del sistema, los ejes variables, los once estilos semánticos, el contrato de Dynamic Type y los casos en que las fuentes personalizadas necesitan trabajo de escalado explícito para participar.
TL;DR
- SF Pro Variable expone tres ejes: peso (
wght), ancho (wdth) y tamaño óptico (opsz)2. El escalado óptico es automático según el tamaño en puntos; el peso y el ancho son direccionables a través deFont.WeightyFont.Widthde SwiftUI. - La familia del sistema incluye SF Pro (predeterminada), SF Pro Rounded (elementos de UI amigables), SF Mono (código, UI técnica), SF Compact (watchOS, contextos estrechos) y New York (compañera serif para lectura editorial).
- Los once estilos de texto de SwiftUI admiten Dynamic Type automáticamente.
.bodyes el predeterminado seguro;.largeTitlehasta.caption2cubren la jerarquía de la plataforma. - Las fuentes personalizadas no escalan con Dynamic Type de manera predeterminada. Usa
Font.custom("Name", size: 16, relativeTo: .body)para que la fuente participe del escalado de Dynamic Type3. @Environment(\.dynamicTypeSize)permite que una vista adapte el diseño al tamaño de texto actual; el rango de valores va desde.xSmallhasta.accessibility5(12 tamaños en total)4.
La familia de fuentes del sistema
Apple distribuye cinco familias que participan de la experiencia de la fuente del sistema.
SF Pro
La predeterminada para todas las plataformas de Apple. iPhone, iPad, Mac y Vision usan SF Pro para texto del cuerpo, titulares y la mayor parte de la UI. La fuente tiene una amplia cobertura de idiomas (latín, griego, cirílico, árabe, hebreo, devanagari, etc.), admite el diseño de derecha a izquierda de forma nativa e incluye variantes diseñadas para versalitas, dígitos alternativos (alineados vs. tabulares) y alternativas estilísticas.
Acceso en SwiftUI a través de la fuente del sistema:
Text("Hello").font(.system(.body)) // SF Pro by default
Text("Hello").font(.system(.body, weight: .semibold)) // SF Pro Semibold
Text("Hello").font(.system(.body, design: .default)) // explicit SF Pro
SF Pro Rounded
Una variante redondeada diseñada para una UI amigable y accesible: complicaciones del Apple Watch, anillos de Fitness, partes de las indicaciones direccionales de Maps, Find My y superficies seleccionadas de Health. Las formas redondeadas comunican suavidad; úsalas por tono, no solo por variedad visual.
Text("Hello").font(.system(.body, design: .rounded))
SF Mono
Familia monoespaciada para código, UI de terminal y cualquier contexto donde importa la alineación de las celdas de caracteres. SF Mono es la fuente predeterminada en Xcode, en la configuración monoespaciada de la app Terminal y en cualquier vista de SwiftUI que solicite .monospaced.
Text("let x = 42").font(.system(.body, design: .monospaced))
SF Compact
Una familia más estrecha que se usa en watchOS, en los resaltados de foco de tvOS y en algunos contextos de Mac donde el espacio horizontal está limitado. Las formas más estrechas conservan la altura de x mientras reducen el avance horizontal, lo que las hace funcionar en las dimensiones de la esfera del Apple Watch donde SF Pro se sentiría apretada.
Las apps de watchOS usan SF Compact automáticamente a través de la fuente del sistema; las apps de iOS generalmente no necesitan solicitarla explícitamente.
New York
Una familia serif diseñada como compañera para la lectura editorial. New York aparece en Books para textos largos, en Notes para notas con estilo manuscrito y en SwiftUI a través del diseño .serif.
Text("Long-form essay").font(.system(.body, design: .serif))
La compañera serif es poco común en la UI de las apps. Recurre a ella deliberadamente (un modo de lectura, un pasaje citado, el cuerpo de un artículo) en lugar de como predeterminada.
Los tres ejes variables
SF Pro Variable codifica tres ejes que se combinan para producir cada glifo:
Peso (wght)
Nueve pesos con nombre: .ultraLight, .thin, .light, .regular, .medium, .semibold, .bold, .heavy, .black. La fuente variable interpola continuamente entre ellos, pero el API de SwiftUI expone los valores con nombre.
Text("Heading").font(.system(.title, weight: .semibold))
El peso comunica la jerarquía de énfasis: .regular para el cuerpo, .semibold o .bold para titulares, .medium para elementos activos de la barra de herramientas, .light para etiquetas sin énfasis. Los estilos semánticos (.headline, .subheadline) vienen con valores predeterminados de peso sensatos; recurre a pesos explícitos solo cuando el estilo semántico no sea la forma correcta.
Ancho (wdth)
Cuatro anchos con nombre en el API de iOS 16+: .compressed, .condensed, .standard, .expanded. El eje de ancho afecta el avance horizontal sin cambiar el peso ni el carácter visual. Úsalo para una UI ajustada (una barra de navegación con muchos elementos) o para textura visual (un titular en tamaño de visualización que quiere más presencia horizontal).
El ancho se aplica a través del modificador de vista .fontWidth(_:) de SwiftUI (o encadenado en una Font mediante .width(_:)):
Text("Compressed")
.font(.system(.title, weight: .bold))
.fontWidth(.compressed)
El ancho rara vez es el eje correcto para el texto del cuerpo; funciona bien en tamaños de visualización donde la tipografía es parte del diseño.
Tamaño óptico (opsz)
El eje técnicamente más ambicioso. Las antiguas variantes SF Text y SF Display de SF Pro ahora son un gradiente continuo codificado en la fuente variable. En tamaños inferiores a 20 puntos, el sistema aplica los ajustes ópticos de SF Text (mayor espaciado entre letras, trazos ligeramente más pesados, contraformas más abiertas). Por encima de 20 puntos, SF Display toma el control (espaciado más ajustado, proporciones refinadas). La transición es fluida.
El eje opsz es automático. Las apps no lo direccionan explícitamente; el sistema lee el tamaño en puntos solicitado y resuelve el escalado óptico correcto. La implicación: la tipografía en tamaños pequeños de UI tiene proporciones distintas que la misma fuente en tamaños de titular, y eso es por diseño. Las fuentes personalizadas que carecen de escalado óptico se ven bien en un tamaño y mal en otros; el manejo automático de SF Pro evita por completo esa trampa.
Los once estilos de texto
Font.TextStyle de SwiftUI define once estilos semánticos que participan todos en Dynamic Type4:
| Estilo | Tamaño predeterminado (Large) | Uso común |
|---|---|---|
.largeTitle |
34 pt | Encabezados de nivel superior, texto destacado |
.title |
28 pt | Encabezados de sección |
.title2 |
22 pt | Encabezados de subsección |
.title3 |
20 pt | Encabezados menores |
.headline |
17 pt | Cuerpo enfatizado, títulos de filas de listas |
.body |
17 pt | Texto del cuerpo predeterminado |
.callout |
16 pt | Cuerpo de apoyo, leyendas en contexto |
.subheadline |
15 pt | Encabezados secundarios, metatexto |
.footnote |
13 pt | Texto pequeño de apoyo |
.caption |
12 pt | Pies de imagen, letra pequeña |
.caption2 |
11 pt | Texto legible mínimo |
La columna “Tamaño predeterminado” muestra el tamaño en la configuración “Large” de Dynamic Type del usuario (el predeterminado del sistema). Cada estilo escala hacia arriba o hacia abajo a medida que el usuario ajusta su tamaño de texto preferido; la jerarquía relativa se mantiene intacta.
La estrategia de adopción correcta es usar los estilos semánticos directamente:
VStack(alignment: .leading) {
Text("Title").font(.title)
Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
Text("Body content here.").font(.body)
}
La jerarquía se preserva en cada configuración de Dynamic Type, se honran las convenciones de la HIG de Apple y la tipografía responde a las preferencias de accesibilidad del usuario sin código por app.
El contrato de Dynamic Type
Dynamic Type es la configuración de tamaño de texto controlada por el usuario en Configuración > Accesibilidad > Pantalla y tamaño del texto > Texto más grande. El valor fluye a través del entorno como DynamicTypeSize, con doce valores desde .xSmall hasta .accessibility54:
- Tamaños estándar:
.xSmall,.small,.medium,.large(predeterminado),.xLarge,.xxLarge,.xxxLarge - Tamaños de accesibilidad:
.accessibility1,.accessibility2,.accessibility3,.accessibility4,.accessibility5
Las apps que usan los estilos de texto semánticos obtienen el escalado gratis. Las apps que quieren adaptar el diseño al tamaño actual leen el entorno:
struct AdaptiveLayout: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
if dynamicTypeSize.isAccessibilitySize {
VStack { content } // stack vertically at accessibility sizes
} else {
HStack { content } // horizontal at standard sizes
}
}
}
La propiedad isAccessibilitySize cubre los cinco tamaños de accesibilidad (donde el texto es lo suficientemente grande como para que los diseños horizontales se rompan con frecuencia). El patrón es el adecuado para cualquier diseño que dependa de que el texto encaje horizontalmente.
Las apps pueden restringir el rango admitido con el modificador .dynamicTypeSize(_:):
ContentView()
.dynamicTypeSize(.large ... .accessibility3)
La restricción recorta la configuración de Dynamic Type para el subárbol modificado. Úsalo cuando el diseño de una vista realmente no pueda acomodar todo el rango; lo predeterminado correcto es admitir cada tamaño y adaptar el diseño en su lugar.
Fuentes personalizadas y Dynamic Type
Las fuentes personalizadas (una fuente de marca distribuida a través del array UIAppFonts del Info.plist) no escalan con Dynamic Type de manera predeterminada. La solución más simple es el parámetro relativeTo: de Font.custom:
// Doesn't scale with Dynamic Type
Text("Brand").font(.custom("MyBrandFont", size: 16))
// Scales with Dynamic Type relative to body
Text("Brand").font(.custom("MyBrandFont", size: 16, relativeTo: .body))
El parámetro relativeTo: le indica a SwiftUI que escale la fuente personalizada usando la curva de escalado del estilo del cuerpo. El tamaño de fuente en la configuración “Large” del usuario es los 16pt solicitados; en configuraciones más grandes, SwiftUI aplica el mismo multiplicador que usaría el estilo del cuerpo.
Para un escalado más sofisticado (curvas distintas en tamaños distintos, manejo óptico personalizado), usa UIFontMetrics de UIKit directamente. El patrón es más extenso pero admite ajustes por tamaño que las fuentes personalizadas a menudo necesitan.
Cuando la tipografía falla
Tres modos de falla que vale la pena nombrar:
Fuentes personalizadas de tamaño fijo en todas partes. El fallo de accesibilidad más común en las apps de iOS: una fuente de marca distribuida con Font.custom("BrandFont", size: 16) (sin relativeTo:) ignora Dynamic Type por completo. Los usuarios con tamaños de texto de accesibilidad ven el texto de la marca a 16pt mientras que el texto del sistema escala a 28pt+; la jerarquía visual se invierte. La solución es relativeTo: en cada uso de fuente personalizada, auditado mediante AccessibilityInspector con la configuración máxima de Dynamic Type.
Pesos codificados a mano para énfasis. Un subtítulo con estilo .font(.body).fontWeight(.bold) es frágil: en tamaños de accesibilidad, el cuerpo en negrita se vuelve casi indistinguible de un cuerpo que ya es grande. El estilo semántico .headline maneja el énfasis correctamente en todo el rango de Dynamic Type; úsalo en lugar de cuerpo+negrita.
Diseños que se rompen en tamaños de accesibilidad. Un stack horizontal de texto + ícono + texto que se desborda en .accessibility3 es un error de diseño que Dynamic Type expone. La solución es el patrón de diseño adaptativo dynamicTypeSize.isAccessibilitySize mencionado arriba; la prueba consiste en ejecutar la app con la configuración máxima de Dynamic Type durante el QA, no solo con el tamaño predeterminado.
Lo que este patrón significa para apps en iOS 26+
Tres conclusiones.
-
Usa los estilos de texto semánticos, no tamaños en puntos ajustados a mano.
.body,.headline,.title2, etc. llevan Dynamic Type, escalado óptico y jerarquía correcta de la plataforma.Font.system(size: 17)ajustado a mano anula cada función del sistema y envejece mal cuando Apple ajusta la rampa predeterminada. -
Pasa siempre
relativeTo:en las fuentes personalizadas. Una fuente de marca distribuida conFont.custom(_, size: _, relativeTo: .body)participa de Dynamic Type. Una fuente de marca distribuida sin él es una regresión de accesibilidad por usuario que QA solo detectará en el tamaño máximo de texto. -
Prueba los diseños en tamaños de Dynamic Type de accesibilidad. La configuración
.accessibility3es aproximadamente 2x el predeterminado Large. Los diseños que se ven bien en tamaños estándar se rompen rutinariamente en tamaños de accesibilidad. La solución es la adaptación a nivel de diseño a través del entornodynamicTypeSize, no excluirse mediante restricciones de.dynamicTypeSize(...).
El cluster completo del Ecosistema Apple: App Intents tipados; servidores MCP; la pregunta del enrutamiento; Foundation Models; la distinción entre LLM de runtime y de tooling; tres superficies; el patrón de fuente única de verdad; Two MCP Servers; hooks para desarrollo en Apple; Live Activities; el contrato de runtime de watchOS; internals de SwiftUI; el modelo mental espacial de RealityKit; disciplina del esquema de SwiftData; patrones de Liquid Glass; distribución multiplataforma; la matriz de la plataforma; framework Vision; Symbol Effects; inferencia con Core ML; API de Writing Tools; Swift Testing; Privacy Manifest; accesibilidad como plataforma; sobre qué me niego a escribir. El hub está en Apple Ecosystem Series. Para un contexto más amplio de iOS con agentes de IA, consulta la guía de iOS Agent Development.
FAQ
¿Cuál es la diferencia entre SF Pro y SF Pro Display / SF Pro Text?
Desde iOS 9 (cuando SF debutó como la fuente del sistema) hasta iOS 16, SF se distribuía como dos fuentes separadas: SF Text para tamaños inferiores a 20pt y SF Display para tamaños de 20pt y superiores, cada una con espaciado entre letras y pesos de trazo ajustados a mano para su rango de tamaño. SF Pro Variable consolida la misma división Text/Display en un eje continuo de tamaño óptico (opsz). Las dos fuentes ya no están separadas; la fuente variable maneja la transición automáticamente según el tamaño en puntos solicitado.
¿Cómo obtengo dígitos monoespaciados en el texto del cuerpo?
Usa .monospacedDigit() en SwiftUI:
Text("\(score)").font(.body).monospacedDigit()
El modificador intercambia los dígitos proporcionales de la fuente del cuerpo por dígitos monoespaciados mientras mantiene proporcional el resto del texto. Úsalo para cualquier UI donde los dígitos deban alinearse entre filas (temporizadores, marcadores, pantallas de saldo).
¿Debería usar SF Pro Rounded para toda la UI?
No. SF Pro Rounded lleva un tono (amigable, accesible) que encaja en algunos contextos y no en otros. Las complicaciones de la app del Watch, el teclado numérico de Phone del iPhone y ciertas superficies de la app Health la usan. Una app de productividad, una app bancaria o una herramienta para desarrolladores generalmente no debería. Recurre a .rounded deliberadamente, no como predeterminada.
¿Cuál es el rango correcto de Dynamic Type para una app de iPhone?
De manera predeterminada, admite cada tamaño desde .xSmall hasta .accessibility5. Los tamaños de accesibilidad (.accessibility1 hasta .accessibility5) son la forma en que los usuarios con baja visión, dificultades motrices u otras necesidades de accesibilidad usan el iPhone. Una app que se excluye mediante restricciones de .dynamicTypeSize(...) falla a esos usuarios. La estrategia correcta es la adaptación del diseño (el patrón isAccessibilitySize), no la exclusión del rango de tamaños.
¿Puedo distribuir una fuente variable personalizada con mi app?
Sí. Las fuentes variables se distribuyen como cualquier otra fuente personalizada (agregar a UIAppFonts del Info.plist, referenciar mediante Font.custom). Para direccionar los ejes variables desde SwiftUI, usa los APIs subyacentes de CTFont de Font.custom a través de UIFontDescriptor.SymbolicTraits o, para control completo del eje, baja a CTFontCreateCopyWithAttributes con kCTFontVariationAttribute. El puente de SwiftUI a los ejes variables es más extenso que para las fuentes del sistema; para la mayoría de las apps, las fuentes del sistema cubren los casos.
Referencias
-
Apple Developer: Fonts. Resumen de la familia de fuentes del sistema, incluidos SF Pro, SF Pro Rounded, SF Mono, SF Compact y New York. ↩
-
Apple Developer: Meet the expanded San Francisco font family (sesión 110381 de WWDC 2022). La introducción del diseño de tres ejes de SF Pro Variable (peso, ancho, tamaño óptico). ↩
-
Documentación de Apple Developer:
Font.custom(_:size:relativeTo:). El inicializador de fuente personalizada que permite que la fuente participe del escalado de Dynamic Type relativo a un estilo de texto elegido. ↩ -
Documentación de Apple Developer:
DynamicTypeSize. La enumeración de doce valores desde.xSmallhasta.accessibility5más el predicadoisAccessibilitySizepara la adaptación a nivel de diseño. ↩↩↩