El orden de los modificadores
Al terminar: Ver por qué el orden de los modifiers cambia lo que se pinta.
Al terminar, la pestaña «Laboratorio» de Marcador enseñará seis recuadros que demuestran, con colores y bordes, que el orden de los modificadores cambia lo que se pinta; y una prueba de interfaz comprobará que están ahí. Es lo contrario de Compose, donde el primer modificador es el de fuera.
Importa porque es la causa de la mayoría de los «¿por qué mi fondo no cubre el margen?» en SwiftUI, y una pregunta frecuente de entrevista.
El orden de los modificadores importa (y es al revés que en Compose)
Un modificador es un método que se llama sobre una vista y devuelve otra: .padding(16) no cambia el Text, devuelve una vista nueva que lo envuelve.
Por eso el orden cambia el resultado: cada modificador envuelve lo que tiene delante. Se lee de arriba abajo, como capas que se añaden alrededor del texto. Dos versiones del mismo texto:
// A) primero el aire y después el color: el color cubre el texto Y el aire.
Text("A · padding y luego background")
.padding(16) // envuelve el texto con 16 puntos de aire
.background(.yellow) // pinta lo que tiene delante: el texto más el aire
// B) primero el color y después el aire: el color cubre solo el texto.
Text("B · background y luego padding")
.background(.yellow) // pinta lo que tiene delante: solo el texto
.padding(16) // añade el aire fuera del colorEsquema (una ilustración, no una captura; · es aire sin color):
A) padding y luego background B) background y luego padding
███████████████ ···············
█ █ ··███████████··
█ A · texto █ ··█ B · texto █··
█ █ ··███████████··
███████████████ ···············
el color incluye el aire el color pega al textoCuidado con la comparación con Compose. En Compose el primer modificador es la capa de fuera y los siguientes quedan dentro; en SwiftUI, el primero es la capa de dentro y cada siguiente lo envuelve. La misma cadena da resultados opuestos:
| Cadena | Compose | SwiftUI |
|---|---|---|
padding y luego background | El color cubre solo el texto | El color cubre el texto y el aire |
background y luego padding | El color cubre el texto y el aire | El color cubre solo el texto |
La regla práctica en SwiftUI es la de antes: lee la cadena de arriba abajo, como capas que se añaden alrededor del texto (la capa de dentro es la primera). Lo mismo ocurre con .frame y .border: un borde antes de un frame rodea al texto; después, al frame entero. Y es lo que pasaba en la tarjeta de la lección anterior: el fondo va después del padding precisamente para que cubra el aire.
Piezas nuevas
.border(.red): un borde de color, para ver dónde acaba cada vista..accessibilityIdentifier("versionA"): el nombre interno que ya usaste en la etiqueta del controlador (allí con UIKit, aquí con SwiftUI). Las pruebas de interfaz lo usan para encontrar la vista..frame(width: 200): da a la vista un ancho fijo de 200 puntos (.frame(width: 120, height: 40)fija también el alto; elframe(maxWidth:)de antes la deja crecer)..background(.green.opacity(0.4)): un color con transparencia, para ver elframesin tapar el texto.
Las piezas, sobre dos textos cualquiera:
import SwiftUI
struct Recuadros: View {
var body: some View {
VStack(spacing: 12) {
Text("con borde")
.padding(8)
.border(.red) // un borde rojo alrededor de lo que tiene delante
Text("ancho fijo")
.frame(width: 160) // 160 puntos de ancho, con el texto centrado dentro
.background(.blue.opacity(0.3)) // azul al 30 %: se ve el frame sin tapar el texto
.accessibilityIdentifier("anchoFijo") // nombre interno para las pruebas de interfaz
}
}
}Se ve así: «con borde» con un borde rojo a 8 puntos de las letras (el borde va después del aire, así que lo rodea); y debajo «ancho fijo» sobre una franja azul clara de 160 puntos de ancho, con el texto en el centro.
Reto · Ejercicio: dónde va el borde
Crea una subcarpeta DondeVaElBorde dentro de Ejercicios y, dentro, el fichero DondeVaElBorde.swift: una vista, DondeVaElBorde, con dos textos «Rayo FC» en columna, separados 40 puntos.
- En el primero, el borde rojo rodea solo el texto y el verde cubre el texto con 20 puntos de aire.
- En el segundo, el borde rodea el verde con su aire.
Con su vista previa debe verse: arriba, un recuadro verde con un borde rojo fino pegado a las letras; abajo, el mismo recuadro verde con el borde rojo por fuera. Pista: lee la cadena de arriba abajo, como capas que se añaden alrededor del texto; los tres modificadores son los mismos en las dos cadenas, lo único que cambia es el orden.
Ver solución
import SwiftUI
struct DondeVaElBorde: View {
var body: some View {
VStack(spacing: 40) {
// El borde rodea solo el texto; el verde cubre el texto y 20 puntos de aire.
Text("Rayo FC")
.border(.red)
.padding(20)
.background(.green)
// El borde rodea el verde con su aire: va el último.
Text("Rayo FC")
.padding(20)
.background(.green)
.border(.red)
}
}
}
#Preview {
DondeVaElBorde()
}Cada modificador envuelve a lo que tiene delante. En la primera cadena, el borde se dibuja sobre el texto antes de añadir el aire. En la segunda, el aire y el verde ya existen cuando llega el borde.
Paso 1 · OrdenDeModifiers y el laboratorio
Objetivo: seis recuadros en la pestaña «Laboratorio», debajo del botón de UIKit, que enseñan el orden de los modificadores con colores y bordes.
Crea OrdenDeModifiers.swift en Laboratorio, porque es una prueba para ver cómo funciona SwiftUI, no una parte de Marcador: clic derecho sobre la carpeta Laboratorio → New File from Template… → Swift File → Next, nombre OrdenDeModifiers. Si el menú se llama distinto, touch ~/Marcador-Swift/MarcadorApp/MarcadorApp/Laboratorio/OrdenDeModifiers.swift (la carpeta Laboratorio ya existe, así que no hace falta mkdir).
Intenta escribirlo tú: OrdenDeModifiers, una columna alineada a la izquierda con el título «El orden de los modificadores» y, debajo, seis textos con fondo amarillo o verde: «A · padding y luego background» (16 de aire y después el fondo, con el nombre interno versionA), «B · background y luego padding» (al revés, con versionB), los mismos dos con un borde rojo al final («A con borde» y «B con borde»), y dos con 200 puntos de ancho y un verde translúcido, «frame y luego fondo» y «fondo y luego frame». Cuando termines, abre el bloque.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
import SwiftUI
// Cada modificador devuelve una vista NUEVA que envuelve a la anterior: se lee de arriba abajo, como capas.
struct OrdenDeModifiers: View {
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Text("El orden de los modificadores")
.font(.headline)
// A) primero el padding (el texto más 16 de aire) y luego el fondo, que pinta todo eso.
Text("A · padding y luego background")
.padding(16)
.background(.yellow)
.accessibilityIdentifier("versionA") // un nombre para las pruebas de interfaz
// B) primero el fondo (solo el tamaño del texto) y luego el padding, que añade aire fuera del color.
Text("B · background y luego padding")
.background(.yellow)
.padding(16)
.accessibilityIdentifier("versionB")
// Las mismas dos, con un borde rojo para ver dónde acaba cada una.
Text("A con borde")
.padding(16)
.background(.yellow)
.border(.red)
Text("B con borde")
.background(.yellow)
.padding(16)
.border(.red)
// El `frame` también cuenta: con el fondo después, el color cubre los 200 puntos;
// con el fondo antes, solo cubre el texto.
Text("frame y luego fondo")
.frame(width: 200)
.background(.green.opacity(0.4))
Text("fondo y luego frame")
.background(.green.opacity(0.4))
.frame(width: 200)
}
}
}
#Preview {
OrdenDeModifiers().padding()
}Ahora, en LaboratorioView.swift, pon los recuadros debajo del botón. Añade una línea dentro del VStack, después del botón; lo demás no cambia:
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
VStack(alignment: .leading, spacing: 24) {
Button("Ciclo de vida (UIKit)") { mostrandoControlador = true }
.buttonStyle(.borderedProminent)
OrdenDeModifiers() // NUEVA: los recuadros de esta lección
}Ejecuta con Cmd+R. En «Laboratorio» ves, debajo del botón, los recuadros: en A el amarillo rodea el texto y su aire; en B está pegado al texto. Con el borde rojo se ve dónde acaba cada uno, y en los dos últimos, el verde del frame cubre los 200 puntos o solo el texto, según el orden.
Paso 2 · La prueba
Objetivo: una prueba de interfaz que abre la pestaña «Laboratorio» y comprueba que los recuadros A y B están ahí. Las piezas de siempre están en la tabla de XCUITest.
Esta prueba usa dos piezas que no están todavía en la tabla, así que te la damos hecha y las explicamos aquí:
.framede un elemento (app.staticTexts["versionA"].frame): el rectángulo que ocupa en pantalla, con posición y tamaño;.widthes su ancho en puntos.XCTAssertGreaterThan(x, 0): falla la prueba sixno es mayor que 0; aquí, que el elemento tiene un tamaño real y no está vacío.
testLaboratorioMuestraElOrdenDeLosModificadores arranca la app, toca la pestaña «Laboratorio», espera a ver el texto con el nombre interno versionA, guarda una captura de nombre modificadores y comprueba que A y B existen y tienen un ancho mayor que cero. Añádela entre testArrancaYMuestraElMarcador y testAbreYCierraElControlador, sin tocar ninguna de las dos:
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
// … aquí termina `testArrancaYMuestraElMarcador`, sin cambios …
// }
@MainActor
func testLaboratorioMuestraElOrdenDeLosModificadores() throws {
let app = XCUIApplication()
app.launch()
app.tabBars.buttons["Laboratorio"].tap()
XCTAssertTrue(app.staticTexts["versionA"].waitForExistence(timeout: 5))
capturar(app, "modificadores")
// Los dos existen y ocupan un tamaño real. (El color que los distingue lo ves tú en la captura:
// una prueba no mide el color.)
let a = app.staticTexts["versionA"].frame
let b = app.staticTexts["versionB"].frame
XCTAssertGreaterThan(a.width, 0)
XCTAssertGreaterThan(b.width, 0)
}
// … aquí empieza `testAbreYCierraElControlador`, sin cambios …Pasa todas las pruebas con Cmd+U, o desde la Terminal:
cd ~/Marcador-Swift/MarcadorApp
xcodebuild -scheme MarcadorApp -destination 'platform=iOS Simulator,name=iPhone 17' build testPunto de control
- Desde
~/Marcador-Swift/MarcadorApp,xcodebuild -scheme MarcadorApp -destination 'platform=iOS Simulator,name=iPhone 17' build testtermina con** TEST SUCCEEDED **(con el simulador que tengas: cambiaiPhone 17por un nombre dexcrun simctl list devices available): cinco pruebas, una unitaria y cuatro de interfaz. - La primera pestaña enseña «Marcador», una tarjeta con «Rayo FC», «2 - 1» y «Toros», y los botones «Gol Rayo FC» y «Gol Toros» (no hacen nada).
- En la pestaña «Laboratorio», el texto A tiene el amarillo alrededor del aire y el B lo tiene pegado al texto; el botón «Ciclo de vida (UIKit)» sigue abriendo la hoja.
- El proyecto tiene
Marcador/UI/PantallaMarcadorEstatica.swift,Laboratorio/OrdenDeModifiers.swiftyLaboratorio/LaboratorioView.swiftdentro de la carpetaMarcadorApp, yEjercicios/DondeVaElBordecon su ejercicio.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.Tienes
Text("Hola").padding(16).background(.yellow). ¿Qué cubre el amarillo?2.Tienes
Text("B").background(.yellow).padding(16).border(.red). ¿Qué rodea el borde rojo?3.¿Qué devuelve un modificador como
.padding(16)cuando lo llamas sobre unText?4.¿Por qué el fondo de la tarjeta del marcador va después del
.padding(20)?5.Con la misma cadena
paddingy luegobackground, ¿qué cubre el color en Compose y en SwiftUI?