List와 LazyVStack, VStack
- SwiftUI
- UIKit
- Swift
List에 대해

Swift에서 List는 단일 열에 정렬된 데이터 행을 표시하는 컨테이너입니다.
정적 리스트 생성
struct ContentView: View {
var body: some View {
List {
Text("리스트 1")
Text("리스트 2")
Text("리스트 3")
Text("리스트 4")
}
}
}
위와 같이 코드를 작성하면 아래와 같이 표시됩니다.

텍스트가 아닌 다른 요소를 넣어도 동일하게 List가 동작합니다.
struct ContentView: View {
var body: some View {
List {
Rectangle().frame(width: 100, height: 100)
Circle().frame(width: 100, height: 100)
}
}
}

동적 리스트 생성
List는 동적으로 내부 아이템을 생성할 수 있으며 Identifiable을 준수하는 배열을 통해 간단하게 목록을 생성할 수 있습니다.
struct NameList: Identifiable {
let name: String
let id = UUID()
}
struct ContentView: View {
private var nameList = [
NameList(name: "홍길동"),
NameList(name: "둘리"),
NameList(name: "또치")
]
var body: some View {
List(nameList) {
Text($0.name)
}
}
}

⭐ 이때 UIKit에서는 UITableIvew에서 내용을 표시할 뷰를 Cell이라고 불렀다면 SwiftUI는 row 라고 표현합니다.
이때 ForEach를 사용하여 동적으로도 생성할 수 있습니다.
SwiftUI에서
ForEach는 List처럼 id로 식별할 수 있는 데이터를 받아 동적으로 뷰를 생성하는 역할을 수행합니다.
struct ContentView: View {
var body: some View {
List {
ForEach(0..<30) {
Text("\($0)")
}
}
}
}

정적 + 동적 리스트
위와 같이 정적 + 동적 기능을 한 번에 조합하여 활용할 수 있습니다.
struct ContentView: View {
let food = ["치킨", "피자", "햄버거", "라면"]
let snack = ["프링글스", "포카칩", "치토스"]
var body: some View {
List {
Text("음식")
.fontWeight(.heavy)
ForEach(food, id: \.self) {
Text("\($0)")
}
Text("과자")
.fontWeight(.heavy)
ForEach(snack, id: \.self) {
Text("\($0)")
}
}
}
}
리스트 스타일
struct Row: View {
var body: some View {
Text("Hello, World!")
}
}
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Header"), footer: Text("footer"), content: {
Row()
Row()
Row()
})
Section {
Row()
Row()
Row()
}
}
.listStyle(.automatic)
}
}
List의 listStyle 모디파이어를 통해 리스트의 스타일을 지정할 수 있습니다.






VStack과 LazyVStack
Vstack
Vstack을 사용하여 내부 뷰를 수직으로 쌓을 수 있습니다.
struct ContentView: View {
var body: some View {
VStack {
Text("안녕하세요")
Text("반갑습니다.")
}
}
}

LazyVStack
LazyVStack은 스택이 Lazy하기 때문에 아이템들이 화면에 보일 때 생성된다.
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(1...100, id: \.self) {
Text("\($0)번째 Row")
}
}
}
}
}
LazyVStack은 기본적으로 스크롤을 지원하지 않기 때문에 ForEach를 통해서 Text를 100개 만들어보자.

여기서 의문. 스택은 lazy하기 때문에 아이템들이 화면에 보일 때 생성된다. 이게 무슨 의미인지 테스트해보자.
struct ContentRow: View {
let id: Int
init(id: Int) {
print("\(id)번째 Row가 생성되었습니다.")
self.id = id
}
var body: some View {
Text("\(id)번째 Row")
}
}
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(1...100, id: \.self, content: ContentRow.init)
}
}
}
}
ContentRow의 생성자에 몇번째로 생성되는지를 출력시키도록 한다.

실제로 보면 대략 35번째, 36번째까지보이며 실제 Console을 확인하면 아래와 같이 나온다.
...
35번째 Row가 생성되었습니다.
35번째 Row가 생성되었습니다.
36번째 Row가 생성되었습니다.
36번째 Row가 생성되었습니다.
37번째 Row가 생성되었습니다.
37번째 Row가 생성되었습니다.

계속 스크롤을 진행하여 화면에 보이는 부분별 즉, 렌더링이 되는 부분만 Console에 보이는 것을 보아
아이템들이 화면에 보일 때 생성되는 것을 알 수 있다.
🤔 만약 똑같은 코드인데 LazyVStack이 아니라 그냥 VStack이라면?

LazyVStack과 달리 alignment를 leading으로 설정해도 가운데 정렬이 되는 것 처럼 보이며 콘솔을 보면 그냥 모든 View가 생성되고 있음을 알 수 있다.
🤔 LazyVStack과 VStack은 메모리적으로 차이가 큰가?
위 실험을 통해 아래의 결론을 도출할 수 있다.
- LazyVStack은 각각의 Row가 화면에 렌더링될 때 생성된다!
- VStack은 렌더링과 상관 없이 그냥 전부 생성시킨다.
지금은 아이템이 별로 없지만 Row가 가변적으로 100개, 1000개, 1만개라면 VStack의 경우 메모리가 극심하게 잡아먹을 수 있을 것 같다.
한 번 메모리 사용량을 직접 보자.
코드는 위에서 사용한 것과 동일하며 VStack과 LazyVStack과 동일하다.

