A custom parameter attribute that constructs views from closures. closure 에서 view를 구성하는 사용자 정의 매개변수 속성입니다.
코드로 바로 예시를 들어보자면!
struct ContentView: View {
var body: some View {
VStack { // 여기서 ViewBuilder가 동작
Text("안녕하세요")
Text("반갑습니다")
Button("클릭") {
print("버튼 클릭됨")
}
}
}
}
위 코드에서 VStack 내부의 여러 뷰들이 쉼표없이 자연스럽게 나열이 되어있는데 이것이 가능한 이유가 @ViewBuilder입니다.
public init(@ViewBuilder content: () -> Content)
@ViewBuilder는 여기서 한 개 이상의 뷰를 받아서 하나의 뷰로 조합을 해줍니다.
내부 동작 원리
스위프트가 Function Builder를 인식하면, 함수(혹은 이니셜라이저) 내부에 있는 구문들을 자동으로 buildBlock 메서드에 넘겨 하나의 결과물로 바인딩해서 넘겨줍니다.
VStack {
Text("첫 번째")
Text("두 번째")
}
이 코드는 내부적으로
VStack(content: {
return ViewBuilder.buildBlock(
Text("첫 번째"),
Text("두 번째")
)
})
이렇게 변환됩니다.
@ViewBuilder의 암시적/명시적 사용
SwiftUI에서 @ViewBuilder는 두 가지 방식으로 사용됩니다
1. 암시적(Implicit) 사용
body 프로퍼티는 @ViewBuilder가 암시적으로 적용이 되어있습니다.
struct ContentView: View {
// 암시적으로 @ViewBuilder가 적용되어 있음
var body: some View {
Text("Hello")
Text("World")
}
}
하지만 body외의 다른 프로퍼티나 메소드는기본적으로 ViewBuilder로 유추(infer)하지 않기 때문에 @ViewBuilder를 명시적으로 넣어야 합니다.
2. 명시적(Explicit) 사용
struct ContentView: View {
// ❌ 컴파일 에러: 여러 뷰를 직접 반환할 수 없음
var headerViews: some View {
Text("제목")
Text("부제목")
}
// ✅ @ViewBuilder를 명시적으로 추가
@ViewBuilder
var headerViews: some View {
Text("제목")
Text("부제목")
}
// ✅ 메서드에도 동일하게 적용
@ViewBuilder
func createHeaderViews() -> some View {
Text("제목")
Text("부제목")
}
}
실 활용 예제
1. 커스텀 컨테이너 뷰
struct CardView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.white)
.shadow(radius: 5)
content
.padding()
}
}
}
// 사용 예시
CardView {
VStack {
Text("제목")
Image(systemName: "star")
Text("내용")
}
}
2. 네비게이션 매니저
final class NavigationManager: ObservableObject {
@Published var selectedTab: TabType = .map
@Published var mapPath: [ViewType] = []
@ViewBuilder
func build(_ view: ViewType) -> some View {
switch view {
case .searchView:
SearchView()
case .detailView(let postId):
DetailView(postId: postId)
case .searchLocationView(locationId: let locationId,
locationTitle: let locationTitle):
SearchLocation(locationId: locationId,
locationTitle: locationTitle)
}
}
}
3. 조건부 렌더링
struct ConditionalView: View {
let isEnabled: Bool
var body: some View {
VStack {
Text("항상 표시")
if isEnabled {
Text("조건부 표시")
}
}
}
}