Skip to content
Todos los artículos

Interlineado en SwiftUI: el modificador lineHeight de iOS 26

El interlineado en SwiftUI con iOS 26: presets de .lineHeight, multiple(factor:), exact y leading, y en qué se diferencia de lineSpacing, en iPhone y iPhone Duo.

Por la redacción de AppleDuoPublicado 7 min de lectura

También disponible en:EnglishDeutschFrançais日本語Türkçe

En esta página
  1. ¿Cómo defino el interlineado en SwiftUI?
  2. ¿Qué significan .tight, .normal y .loose?
  3. ¿Cómo se compara .multiple(factor:) con el line-height de CSS?
  4. ¿Por qué una etiqueta de una línea parece demasiado alta con lineHeight?
  5. ¿Qué diferencia hay entre lineHeight, lineSpacing y leading?
  6. ¿Cómo hago que los números rueden con contentTransition(.numericText())?
  7. ¿Importa más el interlineado en iPhone Duo?
  8. Puntos clave

Los diseñadores entregan especificaciones como «17 pt, interlineado 1.5», y durante mucho tiempo SwiftUI no tuvo una forma directa de expresarlo. Te aproximabas con .lineSpacing, que añade puntos entre líneas, o con el .leading de una fuente, que ofrece tres pasos sutiles. iOS 26 lo resuelve con un modificador real de interlineado en SwiftUI.

Esta guía explica el modificador lineHeight(_:), todos los valores que acepta, cómo se compara con las API anteriores y el comportamiento que sorprende a la gente: dónde va el espacio adicional. Se basa en la documentación de Apple, en el artículo de Natalia Panferova en Nil Coalescing y en publicaciones del desarrollador de iOS @sarunw. El modificador es nuevo en iOS 26.0, y los detalles pueden cambiar entre versiones.

¿Cómo defino el interlineado en SwiftUI?

Aplica .lineHeight(_:) a un Text o a cualquier vista por encima de él. Apple lo describe como un modificador para el interlineado por defecto de la jerarquía de vistas, así que funciona a través del entorno, como .font:

Text(article.body)
    .font(.body)
    .lineHeight(.loose)

El parámetro es un AttributedString.LineHeight opcional, un tipo de Foundation que Apple define como la distancia entre las líneas base de dos líneas consecutivas. El valor por defecto es nil y, en ese caso, según Apple, SwiftUI elige automáticamente un interlineado adecuado para cada contexto. Pasa nil para devolver un subárbol a ese comportamiento automático.

El modificador y el valor de entorno lineHeight correspondiente están disponibles desde iOS 26.0, iPadOS 26.0, macOS 26.0, Mac Catalyst 26.0, tvOS 26.0, visionOS 26.0 y watchOS 26.0. No hay retrocompatibilidad, así que protégelo con #available(iOS 26.0, *) si das soporte a versiones anteriores.

¿Qué significan .tight, .normal y .loose?

AttributedString.LineHeight tiene cuatro propiedades estáticas y tres métodos de fábrica. La documentación de Apple describe cada uno:

ValorDescripción de AppleSe adapta a Dynamic Type
.tightMúltiplo del tamaño en puntos que se percibe como ajustadoSí
.normalMúltiplo del tamaño en puntos que se percibe como normalSí
.looseMúltiplo del tamaño en puntos que se percibe como amplioSí
.variableBasado en el ascendente y el descendente de las fuentes de cada líneaSí
.multiple(factor:)El mayor tamaño en puntos multiplicado por factorSí
.leading(increase:)El mayor tamaño en puntos más increase puntosEn parte: la fuente crece, el incremento no
.exact(points:)Un total fijo en puntosNo

Apple no publica las cifras que hay detrás de los tres presets. Según @sarunw, son atajos de .multiple(factor:): .tight es 1.0, .normal es 1.2 y .loose es 1.5. Si necesitas un valor concreto, escribe tú el factor para que tu código no dependa de esa correspondencia.

La comparación de Nil Coalescing encontró .normal y .variable casi indistinguibles del valor por defecto. La diferencia está en la definición: .variable sigue las métricas de la fuente de cada línea, mientras que los demás valores son constantes con independencia, en palabras de Apple, del ascendente y el descendente efectivos de las fuentes en uso.

¿Cómo se compara .multiple(factor:) con el line-height de CSS?

Si vienes de la web, .multiple(factor:) es el que buscas. Como dice @sarunw, .lineHeight(.multiple(factor: 1.5)) funciona como line-height: 1.5 en CSS: el interlineado es el tamaño de la fuente multiplicado por el factor.

La definición de Apple dice lo mismo, con un matiz: el factor multiplica el mayor tamaño en puntos de la línea, así que una línea que mezcla un cuerpo de 17 pt con una frase intercalada de 22 pt usa 22 pt.

Text(article.body)
    .font(.body)
    .lineHeight(.multiple(factor: 1.5))

Como .body es un estilo de texto, el tamaño en puntos cambia con el ajuste de Dynamic Type de cada persona, y el interlineado cambia con él. Nil Coalescing confirma que multiple(factor:) mantiene el espaciado proporcional en todos los tamaños de Dynamic Type.

Los otros dos métodos cambian esa flexibilidad por control:

Text(caption)
    .lineHeight(.leading(increase: 6))

Text(ticketNumber)
    .lineHeight(.exact(points: 24))

.leading(increase:) es el mayor tamaño en puntos más un número fijo de puntos, así que el hueco se mantiene igual aunque la fuente crezca. .exact(points:) ignora la fuente por completo. Nil Coalescing advierte que, con un valor exacto, las líneas pueden superponerse o recortarse en tamaños de texto grandes, así que resérvalo, si acaso, para textos cortos de tamaño fijo.

¿Por qué una etiqueta de una línea parece demasiado alta con lineHeight?

Este es el truco. Un navegador reparte por la mitad el espacio adicional de un line-height de CSS, por encima y por debajo del texto; la especificación de CSS lo llama half-leading. Según @sarunw, el .lineHeight de SwiftUI coloca todo el espacio adicional debajo de cada línea, incluso cuando solo hay una. Por eso, una etiqueta de una línea con .multiple(factor: 2) parece estar colocada demasiado arriba.

En un párrafo no hace daño: el espacio acaba entre las líneas, que es lo que querías. En una etiqueta de una sola línea dentro de un botón, una ficha o una fila centrada, el texto queda en la parte superior de un marco más alto, y centrar el marco no centra el texto.

Algunas formas de resolverlo:

  • Limita el alcance del modificador. Como .lineHeight se propaga por el entorno, aplícalo al Text del párrafo, no a toda la pantalla. Si lo define una vista superior, restablece una etiqueta con .lineHeight(nil).
  • Alinea por líneas base. En una fila, usa HStack(alignment: .firstTextBaseline) o .lastTextBaseline para que las etiquetas se alineen por su texto, no por sus marcos, y el espacio adicional bajo una línea no las desplace.
  • Usa factores moderados en textos cortos. Un factor grande ayuda sobre todo a la lectura de textos largos. Los titulares y las etiquetas rara vez necesitan más que el valor por defecto.
HStack(alignment: .firstTextBaseline) {
    Text(item.title)
        .font(.headline)
    Spacer()
    Text(item.price, format: .currency(code: "USD"))
        .font(.subheadline)
}
.lineHeight(nil)

Aquí la fila ignora cualquier interlineado definido más arriba, y las dos etiquetas se alinean por su primera línea base. Prueba ambas con tamaños grandes de Dynamic Type, porque es ahí donde los problemas de espaciado aparecen primero.

¿Qué diferencia hay entre lineHeight, lineSpacing y leading?

SwiftUI tiene ahora tres formas de cambiar el espacio entre líneas, y miden cosas distintas:

APIDesdeQué defineUnidad
.lineHeight(_:)iOS 26De línea base a línea baseFactor o puntos
.lineSpacing(_:)iOS 13De la parte inferior de una línea a la superior de la siguientePuntos
Font.leading(_:)iOS 14El espaciado de líneas propio de la fuente.standard, .loose, .tight

lineSpacing(_:) añade un hueco fijo entre líneas. Es útil, pero no se corresponde con una especificación de diseño que da el interlineado como múltiplo, y el hueco no crece con Dynamic Type. El ejemplo de Apple usa .lineSpacing(10).

Font.leading(_:) se aplica a una fuente, como en .font(.title.leading(.loose)). Apple señala que la disponibilidad de los ajustes de leading depende de la fuente, y en algunas fuentes el modificador no tiene efecto. Nil Coalescing describe su efecto como muy sutil.

Para código nuevo en iOS 26 y posterior, .lineHeight es el que se corresponde con la forma en que los diseñadores especifican el texto. Mantén .lineSpacing para deployment targets anteriores.

¿Cómo hago que los números rueden con contentTransition(.numericText())?

Un efecto de texto relacionado, y muy pedido para puntuaciones, contadores y precios: @sarunw señala que .contentTransition(.numericText()) hace que los números rueden hasta su nuevo valor. En lugar de un fundido, cada dígito que cambia se desliza hasta su nuevo valor.

struct ScoreView: View {
    @State private var score = 0

    var body: some View {
        VStack {
            Text(score, format: .number)
                .font(.largeTitle.monospacedDigit())
                .contentTransition(.numericText(value: Double(score)))
            Button("Add 10") {
                withAnimation { score += 10 }
            }
        }
    }
}

Dos detalles lo hacen funcionar. Primero, el consejo de Apple sobre contentTransition(_:) dice que solo tiene efecto dentro de una animación, así que cambia el valor dentro de withAnimation. Segundo, numericText(value:), de iOS 17, usa la diferencia entre el valor anterior y el nuevo para elegir la dirección. El anterior numericText(countsDown:), de iOS 16, recibe en cambio la dirección como un booleano.

La fuente con .monospacedDigit() mantiene estable el ancho del número mientras cambian los dígitos. Para animar símbolos junto a tus números, consulta nuestra guía de animaciones y modos de renderizado de SF Symbols.

¿Importa más el interlineado en iPhone Duo?

En iPhone Duo, la pantalla interior informa un ancho regular, como un iPad, mientras que la pantalla exterior se comporta como otros iPhone (consulta nuestra guía de size classes en iPhone Duo). Una vista de lectura que ocupa la pantalla interior puede acabar con líneas mucho más largas que en la exterior.

Las líneas más largas suelen leerse mejor con algo más de interlineado, y un valor que se ve bien en la pantalla exterior puede resultar apretado en la interior. Como .lineHeight es un valor de entorno, puedes vincularlo a la size class:

@Environment(\.horizontalSizeClass) private var horizontalSizeClass

var body: some View {
    Text(article.body)
        .font(.body)
        .lineHeight(.multiple(factor: horizontalSizeClass == .regular ? 1.5 : 1.3))
        .frame(maxWidth: 680)
}

Los factores de este ejemplo son orientativos, no una pauta de Apple. Limitar el ancho con frame(maxWidth:) suele ayudar más que cualquier interlineado, porque mantiene una longitud de línea cómoda en la pantalla más ancha.

Puntos clave

  • iOS 26 añade .lineHeight(_:), que recibe un AttributedString.LineHeight y se propaga por el entorno. nil significa automático.
  • .multiple(factor: 1.5) funciona como line-height: 1.5 en CSS y se adapta a Dynamic Type. Según @sarunw, .tight, .normal y .loose son los factores 1.0, 1.2 y 1.5.
  • .leading(increase:) añade puntos fijos; .exact(points:) no se adapta y puede recortar el texto en tamaños grandes.
  • Todo el espacio adicional va debajo de cada línea, así que las etiquetas de una línea parecen altas. Limita el alcance del modificador y alinea las filas por las líneas base del texto.
  • .lineHeight mide de línea base a línea base; .lineSpacing añade un hueco entre líneas; Font.leading ofrece tres pasos sutiles.
  • Para números que ruedan, usa .contentTransition(.numericText(value:)) dentro de withAnimation.

A continuación, mira cómo gestiona iOS 27 arrastrar para reordenar y las acciones de deslizamiento en SwiftUI o explora los juegos creados para el plegado, apps que ya se adaptan a las dos pantallas de iPhone Duo.

Preguntas frecuentes

¿Cómo defino el interlineado en SwiftUI?

En iOS 26 y posterior, aplica .lineHeight(_:) a un Text o a cualquier vista superior, por ejemplo .lineHeight(.multiple(factor: 1.5)). El valor se propaga por el entorno. Pasar nil restaura el interlineado automático de SwiftUI.

¿Qué valores usan .tight, .normal y .loose?

Apple los documenta como interlineados constantes basados en un múltiplo del tamaño en puntos que se percibe como ajustado, normal y amplio. El desarrollador @sarunw indica que equivalen a .multiple(factor:) con 1.0, 1.2 y 1.5.

¿Qué diferencia hay entre lineHeight y lineSpacing en SwiftUI?

lineSpacing(_:) añade espacio entre la parte inferior de una línea y la superior de la siguiente, en puntos. lineHeight(_:) define la distancia de una línea base a la siguiente, ya sea como múltiplo del tamaño de la fuente o como valor en puntos.

¿El lineHeight de SwiftUI se adapta a Dynamic Type?

multiple(factor:) sí, porque multiplica el tamaño en puntos. leading(increase:) crece con la fuente, pero añade un número fijo de puntos. exact(points:) es un total fijo, así que no se adapta y puede recortar o superponer el texto en tamaños grandes.

¿Por qué mi etiqueta de una línea parece demasiado alta con lineHeight?

Como señala @sarunw, .lineHeight coloca todo el espacio adicional debajo de cada línea, incluso cuando solo hay una. Aplica factores grandes solo al texto de varias líneas y alinea las líneas sueltas por su línea base en lugar de centrar sus marcos.

Fuentes

  1. lineHeight(_:) · Apple Developer Documentation
  2. AttributedString.LineHeight · Apple Developer Documentation
  3. multiple(factor:) · Apple Developer Documentation
  4. exact(points:) · Apple Developer Documentation
  5. leading(increase:) · Apple Developer Documentation
  6. lineSpacing(_:) · Apple Developer Documentation
  7. Font.Leading · Apple Developer Documentation
  8. numericText(value:) · Apple Developer Documentation
  9. contentTransition(_:) · Apple Developer Documentation
  10. iOS 26 adds a .lineHeight modifier to SwiftUI · @sarunw on X
  11. .lineHeight presets .tight, .normal, .loose = .multiple(factor:) 1.0, 1.2, 1.5 · @sarunw on X
  12. .lineHeight(.multiple(factor: 1.5)) works like CSS line-height: 1.5 · @sarunw on X
  13. .lineHeight puts all extra space below each line · @sarunw on X
  14. .contentTransition(.numericText()) makes numbers roll to their new value · @sarunw on X
  15. Adjusting line height in SwiftUI on iOS 26 · Nil Coalescing
  16. CSS 2.2: Visual formatting model details (line-height) · W3C

Haz que el pliegue forme parte de tu app.

Mostramos apps y juegos que sacan partido de verdad al iPhone Duo. Envíanos la tuya con un enlace al App Store, o mira lo que han creado otros desarrolladores.

[email protected]