SwiftUI에서 수직 레이아웃을 구성할 때 VStack
과 LazyVStack
중 어떤 것을 선택해야 할지 고민해본 적 있나요? 최근 개인정보처리방침 화면을 구현하면서 스크롤 감지 기능이 예상과 다르게 동작하는 문제를 겪었는데, 이를 통해 두 컴포넌트의 중요한 차이점을 발견했습니다.
개인정보처리방침을 끝까지 읽어야만 동의 버튼이 활성화되도록 구현하려고 했습니다. 스크롤 맨 아래에 투명한 뷰를 두고 onAppear
로 감지하는 방식을 사용했죠.
VStack(alignment: .leading, spacing: 20) {
privacyPolicyContent
// 스크롤 끝 감지용
Color.clear
.frame(height: 1)
.onAppear {
hasScrolledToBottom = true // 버튼 활성화
}
}
결과: 화면이 로드되자마자 버튼이 활성화됨...
VStack {
Text("첫 번째") // ✅ 즉시 생성 및 렌더링
Text("두 번째") // ✅ 즉시 생성 및 렌더링
Text("세 번째") // ✅ 즉시 생성 및 렌더링
// ... 100개여도 모두 즉시 생성
}
onAppear
트리거VStack
생성privacyPolicyContent
+ Color.clear
모두 즉시 메모리에 로드Color.clear
의 onAppear
바로 실행 → hasScrolledToBottom = true
LazyVStack {
Text("첫 번째") // ✅ 화면에 보이므로 즉시 생성
Text("두 번째") // ⏳ 스크롤해서 보일 때 생성
Text("세 번째") // ⏳ 스크롤해서 보일 때 생성
// ... 필요할 때만 생성
}
LazyVStack(alignment: .leading, spacing: 20) {
privacyPolicyContent
Color.clear
.frame(height: 1)
.onAppear {
withAnimation(.spring()) {
hasScrolledToBottom = true // 실제 스크롤했을 때만 실행!
}
}
}
결과: 사용자가 정말로 끝까지 스크롤했을 때만 버튼 활성화 ✅
// ✅ 적은 수의 뷰 (10개 이하)
VStack {
Text("제목")
Image("logo")
Button("시작하기") { }
}
// ✅ 모든 뷰가 화면에 보여야 하는 경우
VStack {
HeaderView()
ContentView()
FooterView()
}
// ✅ 많은 수의 뷰 (리스트, 피드)
LazyVStack {
ForEach(posts, id: \.id) { post in
PostView(post: post) // 스크롤할 때만 로드
}
}
// ✅ onAppear 타이밍이 중요한 경우
LazyVStack {
ContentView()
TriggerView() // 실제 보일 때만 트리거
.onAppear { trackScrollReached() }
}
// ✅ 메모리 최적화가 필요한 경우
LazyVStack {
ForEach(0..<1000) { i in
HeavyContentView(index: i) // 필요할 때만 생성
}
}
이번 경험을 통해 배운 것:
특히 스크롤 감지, 무한 스크롤, 페이지네이션 같은 기능을 구현할 때는 LazyVStack
의 게으른 로딩 특성을 잘 활용하면 더 정확하고 효율적인 구현이 가능할 것 같습니다.