Layout: stacks, grids y espaciado
Coloca las cosas en pantalla con VStack, HStack, ZStack, Spacer y Grid. Alignment, frames y el modelo de layout que hace SwiftUI predecible.
Todo diseño de interfaz es, en el fondo, colocar cosas: en fila, en columna, encima. SwiftUI lo resuelve con contenedores simples que se combinan. Domina cuatro o cinco y podrás construir casi cualquier pantalla.
- Apilar con VStack, HStack y ZStack.
- Empujar y separar con Spacer y spacing.
- Alinear con alignment.
- Rejillas con Grid y control de tamaño con frame.
Los tres stacks
VStack
Apila en vertical (columna). De arriba abajo.
HStack
Apila en horizontal (fila). De izquierda a derecha.
ZStack
Apila en profundidad (capas). Uno encima de otro.
VStack {
Text("Arriba")
Text("Abajo")
}
HStack {
Image(systemName: "star")
Text("Al lado")
}
ZStack {
Color.blue // fondo
Text("Encima").foregroundStyle(.white)
}
Espaciado y Spacer
Cada stack acepta spacing para separar sus hijos. Y Spacer() es un elemento elástico que empuja el resto:
VStack(spacing: 20) { // 20 puntos entre elementos
Text("Título")
Text("Subtítulo")
}
HStack {
Text("Izquierda")
Spacer() // empuja hacia los extremos
Text("Derecha")
}
En vez de calcular márgenes, deja que Spacer() haga el trabajo: uno antes empuja a la derecha/abajo, uno después a la izquierda/arriba, uno a cada lado centra. Es la forma más natural de distribuir espacio en SwiftUI.
Alignment
Cada stack alinea sus hijos en el eje perpendicular. Un VStack alinea en horizontal:
VStack(alignment: .leading) { // .leading, .center, .trailing
Text("Alineado")
Text("a la izquierda")
}
HStack(alignment: .top) { // .top, .center, .bottom
Text("Arriba")
Text("del todo")
}
Grid
Para rejillas de filas y columnas alineadas, Grid (iOS 16+):
Grid {
GridRow {
Text("Nombre"); Text("Ana")
}
GridRow {
Text("Edad"); Text("30")
}
}
Para colecciones que se desplazan, usarás LazyVGrid / LazyHGrid (cargan solo lo visible), que verás con las listas.
frame: controlar el tamaño
Por defecto, una vista ocupa lo que necesita. frame te deja fijar tamaños o pedir que se expanda:
Text("Botón")
.frame(maxWidth: .infinity) // ocupa todo el ancho disponible
.frame(height: 50)
.background(.blue)
maxWidth: .infinity es el truco para que un elemento se estire hasta llenar su contenedor — habitual en botones a ancho completo.
El modelo mental correcto: el padre propone un tamaño al hijo, el hijo elige el suyo dentro de esa propuesta, y el padre lo coloca. Un Text elige lo justo para su contenido; con .frame(maxWidth: .infinity) elige estirarse. Cuando algo no se coloca donde esperas, piensa en esa conversación padre↔hijo: casi siempre el hijo está eligiendo un tamaño distinto al que imaginabas. Entender esta negociación te ahorra la frustración clásica del principiante con el layout.
- Haz un
VStackcon un título y un subtítulo, alineados a.leading. - Crea una fila (
HStack) con un texto a la izquierda y otro a la derecha usandoSpacer(). - Superpón un
Textsobre unColorconZStack. - Haz un botón a ancho completo con
.frame(maxWidth: .infinity). - Prueba una rejilla simple con
GridyGridRow.