안녕하세요! 피피아노입니다 🎵
이번 포스팅에서는 ViewBuilder에 대해서 정리를 해보려고 합니다.
ViewBuilder란?
Apple의 공식 문서 설명에는 "Closure로부터 View를 구성하는 사용자 지정 매개변수 속성"이라고 합니다.
A custom parameter attribute that constructs views from closures.
https://developer.apple.com/documentation/swiftui/viewbuilder
ViewBuilder | Apple Developer Documentation
A custom parameter attribute that constructs views from closures.
developer.apple.com
일반적으로 'parameter' 속성은 자식 뷰를 생성하는 클로저의 매개변수로 사용되며, 이를 통해 해당 클로저는 여러 개의 자식 뷰를 제공할 수 있습니다.
무슨 말인지는 사용법을 보면서 조금 더 알아보겠습니다.
ViewBuilder 사용법
// 오류 발생: 리턴 타입이 명확하지 않음
func createViews() -> some View {
Text("Hello")
Text("World")
}
Swift 함수는 기본적으로 단일 리턴 타입만 가집니다. 만약 SwiftUI에서 여러 뷰를 그냥 나열하면 컴파일러는 오류를 발생시킵니다.
이럴 때 함수 앞에 @ViewBuilder를 붙이면, Swift는 내부의 뷰들을 모아서 하나의 뷰로 구성해줍니다.
@ViewBuilder
func createViews() -> some View {
Text("Hello")
Text("World")
}
예를 들어서 경우에 따라서 다른 화면을 보여주는 함수를 작성했다고 가정을 해보자면
@ViewBuilder
private var contentView: some View {
switch currentState {
case .loading:
VStack {
ProgressView()
Text("데이터를 불러오는 중...")
}
case .success(let data):
HStack {
Image(systemName: "checkmark.circle.fill")
Text("데이터 로드 성공: \(data)")
}
case .failure(let errorMessage):
VStack {
Image(systemName: "exclamationmark.triangle.fill")
Text("에러 발생").font(.headline)
Text(errorMessage)
}
}
}
}
코드를 보면 switch를 사용헤서 currentState에 따라 다른 뷰 구조를 반환합니다.
근데 loading 상태에서는 VStack, Success 상태에서는 HStack, failure 상태에서는 다시 VStack을 반환합니다.
지금 이렇게 반환 타입이 다르기 때문에 이 함수는 오류가 나게 됩니다. 하지만 함수 위에 @ViewBuilder를 작성해주면 이들을 내부적으로 _ConditionalContent라는 특별한 뷰 타입으로 변환하여 하나의 일관된 some View로 만들어줍니다.
근데 여기서 드는 의문점은 왜 some View로 만들어주냐?
SwiftUI의 뷰는 중첩될수록 타입이 굉장히 복잡해집니다. 만약 some View가 없다면 우리는 함수 리턴 타입을 전부 다 적어줘야 합니다.
// some View가 없다면 예상되는 끔찍한 타입 노출
func MyView() -> VStack<TupleView<(Text, HStack<Button<Text>>)>> {
VStack {
Text("Hello")
HStack {
Button("Click") { }
}
}
}
이런 복잡한 타입을 개발자가 모두 작성해주는 건 너무 어렵고 불가능하기 때문에 some View가 복잡한 진짜 타입을 숨기고
"이건 View 프로토콜을 따르는 무언가야"라고 말할 수 있게 됩니다.
그래서 우리가 SwiftUI로 View를 만들 때
var body: some View {
}
이 안에 코드를 작성해주는 것입니다.
그런데 여기서 한 가지 알아야 하는 것이 있습니다.
우리가 SwiftUI에서 매일 쓰는 body 프로퍼티를 다시 보면
var body: some View {
Text("Hello")
Text("World")
}
뭔가 이상합니다.
분명 위에서 여러 뷰를 그냥 나열하면 에러가 난다고 했는데 왜 body 안에서는 아무런 에러 없이 동작할까요?
그 이유는 View 프로토콜 정의에 숨겨져 있습니다.

body 정의를 보면
@ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
이렇게 body 프로퍼티 앞에 @ViewBuilder가 붙어 있습니다.
즉, 우리가 써주지 않아도 이미 body 자체에 @ViewBuilder로 선언이 되어 있기 때문에 써줄 필요가 없는 겁니다.
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > SwiftUI' 카테고리의 다른 글
| [SwiftUI] NavigationLink & NavigationStack (0) | 2025.10.22 |
|---|---|
| [SwiftUI] 앱에 Face ID 잠금 기능 적용하기 (8) | 2025.07.08 |
| [SwiftUI] SwiftUI 상태 동기화 트러블슈팅 (1) | 2025.06.25 |
| [SwiftUI] CADisplayLink를 활용한 부드러운 ProgressView 애니메이션 구현 트러블슈팅 (6) | 2025.06.06 |
| [SwiftUI] NavigationStack 사용 시 화면 전환 안 되는 문제와 title 깨짐 문제 트러블슈팅 (0) | 2025.06.03 |
