Si construiste una lista en SwiftUI usando ScrollView con VStack, seguramente notaste que se ve bien pero no rinde igual. Aquí aprenderás cómo usar el componente nativo List de SwiftUI para ahorrar memoria, personalizar su apariencia y conectarla a un modelo de datos real.
Por qué usar List en lugar de ScrollView con VStack
Un ScrollView con VStack renderiza todos los elementos al mismo tiempo, aunque el usuario solo vea unos pocos en pantalla. Si tu lista tiene 65 elementos y solo 10 son visibles, los otros 55 siguen consumiendo memoria.
List resuelve eso: solo renderiza los elementos visibles, lo que la hace más rápida y eficiente.
¿Cuál es la diferencia entre List y ScrollView en SwiftUI?ScrollView dibuja todo el contenido aunque no se vea. List renderiza solo lo visible, ahorrando memoria y mejorando el rendimiento.
Cómo migrar de ScrollView a List
El cambio es directo. Tomas el ForEach que tenías dentro del VStack, eliminas la combinación ScrollView + VStack y lo envuelves en List.
swift
List {
ForEach(0..<10) { _ in
SmallCardView()
}
}
Con eso ya tienes una lista nativa funcionando en el preview [0:45].
Cómo personalizar el estilo de una List en SwiftUI
Al usar List notarás tres cosas nuevas: un fondo gris, un fondo blanco interno y separadores grises entre cada fila. Si quieres una lista limpia, tienes que removerlos con modifiers.
Para quitar el fondo gris, aplica .listStyle(.plain) a nivel de la lista. Así eliminas el estilo agrupado por defecto y queda un fondo plano.
Para quitar los separadores entre filas, usa .listRowSeparator(.hidden) a nivel del elemento hijo, no de la lista. Si lo pones solo en el primer elemento, solo desaparece ahí, así que conviene incluirlo dentro de la definición de cada vista de carta.
swift
List {
ForEach(cards) { card in
SmallCardView(card: card)
}
}
.listStyle(.plain)
¿Cómo quitar los separadores de una List en SwiftUI? Aplica el modifier .listRowSeparator(.hidden) directamente en cada vista hija de la lista, no en la lista misma.
Cómo conectar una List con un modelo de datos
Una lista que repite la misma carta no sirve de mucho. Necesitas un modelo que represente cada elemento y un array que los almacene.
Definir el modelo con struct y enum
Arriba de tu ContentView, define una estructura llamada NCard con tres propiedades: title, text y type. El tipo lo modelas con un enum para limitar las opciones a dos casos: pequeño y mediano.
swift
struct NCard: Identifiable {
let id = UUID()
let title: String
let text: String
let type: NCardType
}
enum NCardType {
case small
case medium
}
Usar un struct te da un modelo inmutable y predecible. Usar un enum para el tipo evita que alguien pase un valor inválido como “grande raro” o un string mal escrito.
Por qué tu modelo necesita Identifiable
Cuando intentas iterar un array con ForEach, SwiftUI te marca un error si tu modelo no conforma el protocolo Identifiable. Este protocolo le dice a SwiftUI cómo distinguir cada elemento de los demás.
La forma más rápida de cumplirlo es agregar una propiedad id de tipo UUID, que genera un identificador único para cada instancia.
¿Qué hace el protocolo Identifiable en SwiftUI? Le da a cada elemento un identificador único para que ForEach y List puedan rastrearlo y actualizarlo de forma eficiente.
Cómo iterar el array y pintar contenido dinámico
Dentro de tu View declara la propiedad cards con tu array de datos precargados. Por ejemplo, tres cartas pequeñas y una mediana.
Luego cambias el ForEach para que itere sobre cards en lugar de un rango numérico. Cada iteración recibe una card que puedes pasarle a tu vista hija.
Pasar la carta como parámetro a la vista hija
La vista de la carta pequeña necesita aceptar un parámetro de tipo NCard. Dentro de la vista, reemplazas los textos quemados por card.title y card.text.
swift
struct SmallCardView: View {
let card: NCard
varbody: some View{VStack{Text(card.title)Text(card.text)}.listRowSeparator(.hidden)}
}
En el preview verás que cada fila muestra contenido diferente. Carta 1 distinta de Carta 2, distinta de Carta 3.
Reto: agregar la carta mediana
Hasta aquí solo estás pintando cartas pequeñas. Tu siguiente paso es detectar el type de cada NCard dentro del ForEach y mostrar la vista mediana cuando corresponda. Una pista: un if o un switch sobre card.type te resuelve el caso.
¿Cómo lo resolverías tú? Cuéntame en los comentarios qué enfoque usaste.
Les comparto la lista de Cards de ejemplo ya transcrita:
letcards:[NCard]=[NCard(title:"Card 1",text:"Texto del card 1",type:.small),NCard(title:"Card 2",text:"Texto del card 2",type:.medium),NCard(title:"Card 3",text:"Texto del card 3",type:.small),NCard(title:"Card 4",text:"Texto del card 4",type:.small)]
Con las tarjetas de distintos tamaños:
c
En la interfaz de la clase, no puedo acceder a la seccion de recursos. Que puedo hacer? No me aparece esa seccion.
Aqui esta el resultado del cambio:
con ese cambio me muestra asi :)
si alguno se preguntaba que se muestre en base al type , seria así :
importSwiftUIstruct NCard:Identifiable{var id =UUID()lettitle:Stringlettext:Stringlettype:TypeCard}enumTypeCard{case small
case medium
}struct ContentView:View{let colorBlue =Color(red:208/256,green:222/256,blue:241/256)letcards:[NCard]=[NCard(title:"Card 1",text:"Texto del card 1",type:.small),NCard(title:"Card 2",text:"Texto del card 2",type:.medium),NCard(title:"Card 3",text:"Texto del card 3",type:.small),NCard(title:"Card 4",text:"Texto del card 4",type:.small)] @ViewBuilder func cardSmall(card:NCard)-> some View{HStack(alignment:.center,spacing:20){VStack{Text(card.title).bold()}.frame(width:150,height:40).padding(5).background(colorBlue).cornerRadius(10)HStack{Text(card.text)Image(systemName:"heart").foregroundStyle(.red)}}.frame(width:350,height:50).padding(20).background(.white).cornerRadius(20)} @ViewBuilder func cardLarge(card:NCard)-> some View{VStack(alignment:.center){Image(systemName:"heart").foregroundStyle(.red).frame(maxWidth:.infinity,alignment:.trailing)VStack{Text(card.title).bold()}.frame(width:150,height:40).padding(5).background(colorBlue).cornerRadius(10)HStack{Text(card.text)}}.frame(width:350,height:150).padding(20).background(.white).cornerRadius(20)}varbody: some View{List{ForEach(cards,id: \.id){ card inif card.type==.small{cardSmall(card: card)}if card.type==.medium{cardLarge(card: card)}}}.listStyle(.plain)}}#Preview{ContentView()}```**import**SwiftUI**struct**NCard:Identifiable{**var** id =UUID()**let** title:String**let** text:String**let** type:TypeCard}**enum**TypeCard{**case** small
**case** medium
}**struct**ContentView:View{**let** colorBlue =Color(red:208/256,green:222/256,blue:241/256)**let** cards: \[NCard]= \[NCard(title:"Card 1",text:"Texto del card 1",type:.small),NCard(title:"Card 2",text:"Texto del card 2",type:.medium),NCard(title:"Card 3",text:"Texto del card 3",type:.small),NCard(title:"Card 4",text:"Texto del card 4",type:.small)]@ViewBuilder**func**cardSmall(card:NCard)->**some**View{HStack(alignment:.center,spacing:20){VStack{Text(card.title).bold()}.frame(width:150,height:40).padding(5).background(colorBlue).cornerRadius(10)HStack{Text(card.text)Image(systemName:"heart").foregroundStyle(.red)}}.frame(width:350,height:50).padding(20).background(.white).cornerRadius(20)}@ViewBuilder**func**cardLarge(card:NCard)->**some**View{VStack(alignment:.center){Image(systemName:"heart").foregroundStyle(.red).frame(maxWidth:.infinity,alignment:.trailing)VStack{Text(card.title).bold()}.frame(width:150,height:40).padding(5).background(colorBlue).cornerRadius(10)HStack{Text(card.text)}}.frame(width:350,height:150).padding(20).background(.white).cornerRadius(20)}**var** body:**some**View{List{ForEach(cards,id: \\.id){ card **in****if** card.type==.small{cardSmall(card: card)}**if** card.type==.medium{cardLarge(card: card)}}}.listStyle(.plain)}}\#Preview{ContentView()}