En su documentación, Apple define los ViewBuilders como un elemento personalizado que construye una vista a partir de un closure
.
Tampoco es que se demasiado clara esta definición, pero básicamente te permite generar sub-vistas, componentizar el código, vamos.
Para este ejemplo vamos a crear un Grid (compatible con iOS15), utilizando un @ViewBuilder, que pueda elegir hacerlo de 2 x 2 y de 1 x 4, por ejemplo. Los iconos son los mismos, de hecho uno de ellos tiene que ser un botón que permite marcar como favorito y el resto simplemente vistas.
Los intentaré simplificar para centrarnos en el @ViewBuilder
.
Todo comienza con el struct Icon
, con las propiedades tipo de icono y si se debe mostrar.
struct Icon {
let type: IconType
var show: Bool
var image: Image {
switch type {<
case .isFavorite: Image(systemName:"heart")
case .isOpen: Image(systemName: show ? "door.left.hand.open":"door.left.hand.closed")
case .horario(let openHours): Image(systemName: openHours.imageName)
case .guardia: Image(systemName: "cross")
}
}
}
La imagen para cada icono es distinta, así que utilicé 2 enums
, dado que en el caso de las OpenHours
están las opciones de 24horas, 12 horas y 8 horas.
enum IconType {
case isFavorite, isOpen, horario(OpenHours), guardia
}
enum OpenHours {
case _24h, _12h, _8h
var imageName: String {
switch self {
case ._24h: "24.circle"
case ._12h: "12.circle"
case ._8h: "bag.circle"
}
}
}
Del struct Icon cree una extensión con funciones estáticas que devuelven una View
, que me permitirán, en el caso del icono favorito añadir un botón, y en el resto solo la View
.
extension Icon {
static func favorite(isFavorite: Bool, action: @escaping ()->() ) -> some View {
let icon = Icon(type: .isFavorite, show: isFavorite)
return Button {
action()
} label: {
IconView(icon: icon)
}
}
static func isOpen(_ show: Bool) -> some View {
let icon = Icon(type: .isOpen, show: show)
return IconView(icon: icon)
}
static func horario(_ openHours: OpenHours) -> some View {
let icon = Icon(type: .horario(openHours), show: true)
return IconView(icon: icon)
}
static func guardia(show: Bool) -> some View {
let icon = Icon(type: .guardia, show: show)
return IconView(icon: icon)
}
}
Y ahora si la función que implementa el @ViewBuilder
, que también es una función estática de Icon
.
static func Grid<IconGrid: View>(rows: Int,
verticalSpacing: CGFloat = 10,
horizontalSpacing:CGFloat = 10,
@ViewBuilder icons: () -> IconGrid)
-> some View {
let rows = Array.init(repeating: GridItem(.fixed(verticalSpacing * 4)), count: rows)
return LazyHGrid(rows: rows,spacing: horizontalSpacing) {
icons()
}
}
La función Grid recibe los siguientes parámetros:
rows: Cuántas filas tendrá el grid.
vertical y horizontal spacing: Permitirán ajustar el espacio entre los iconos.
icons: Es cada elemento del closure
que será parte del Grid
. Se marca con el atributo @ViewBuilder
para que Swift sepa que devolveremos una View
, anclada en el tipo <IconGrid: View>
En este caso utilizo un LazyHGrid
, que funciona similar a un HStack
, y que el atributo lazy significa que la vista es creada cuando SwiftUI lo necesite.
Ahora podemos utilizar, a partir de nuestro struct Icon
esta vista, indicando las filas que necesitamos y dentro del closure
todas las vistas que queremos mostrar.
struct ListIconView: View {
@State var tiendas = Tienda.samples
var body: some View {
ForEach(tiendas) { tienda in
HStack {
Text("Hello, World!")
Spacer()
// Aqui está implementado:
Icon.Grid(rows: 2) {
Icon.favorite(isFavorite: tienda.isFavorite) {
marcaComoFavorita(tienda: tienda)
}
.foregroundColor(Color.red)
Icon.guardia(show: tienda.guardia)
.foregroundColor(Color.blue)
Icon.isOpen(tienda.isOpen)
Icon.horario(tienda.horario)
.foregroundColor(Color.yellow)
}
.font(.largeTitle)
}
.padding()
}
}
private func marcaComoFavorita(tienda: Tienda) {
guard let index = tiendas.firstIndex(where:{ $0.id == tienda.id }) else { return }
tiendas[index].isFavorite.toggle()
}
}
¿Que te ha parecido?
Te animo a implementar una función en la que puedas elegir entre un LazyVStack
o LazyHStack
y me cuentes.
¿Quieres recibir posts, cheatCodes, enlaces y katas en Swift para practicar?
Quincenalmente recibirás en tu correo electrónico la newsletter, solo hace falta tu correo electrónico.