@ViewBuilder: SwiftUI에서 View를 위한 result builder란

저자: IT Sectr 게시일: 2026-06-24 읽는 시간: 7 분

@ViewBuilder는 선언적 View 계층 구조 구축을 위해 설계된 SwiftUI의 result builder 어노테이션입니다. Apple Developer Documentation, 2024에 따르면, @ViewBuilder는 여러 표현식과 조건 로직이 포함된 코드 블록을 Swift 컴파일러가 이해할 수 있는 단일 View 타입으로 변환합니다. 이 어노테이션이 없으면 if/else 및 body의 여러 요소와 함께 익숙한 선언적 SwiftUI 구문을 사용하는 것이 불가능합니다.

핵심 요점

  • @ViewBuilder — 여러 View를 추가 컨테이너 없이 하나의 구성으로 조합하는 result builder
  • buildBlock — 표현식 시퀀스를 최대 10개 요소의 TupleView로 래핑
  • buildEither — if/else 및 switch 분기를 위한 ConditionalContent 생성
  • 제한 사항 — Group 또는 ForEach 없이 단일 블록에 최대 10개 요소
  • 암시적 적용 — body는 이미 @ViewBuilder로 래핑되어 있으며, 사용자 정의 함수에는 명시적 어노테이션 필요

SwiftUI에서 @ViewBuilder란?

@ViewBuilder는 result builder 패턴(SE-0289)을 구현하는 어노테이션으로, 선언적 구문을 사용하여 여러 View를 하나의 구성으로 조합할 수 있게 합니다. 여러 표현식, 조건부 구문 및 옵셔널 값을 해당 타입(TupleView, ConditionalContent, OptionalContent)으로 자동 래핑합니다.

result builder가 등장하기 전에는 개발자가 수동으로 요소를 VStack이나 HStack으로 래핑하고, 조건 로직에 삼항 연산자나 팩토리 메서드를 사용해야 했습니다. @ViewBuilder는 SwiftUI 구문을 간결하고 읽기 쉽게 만들어 if/else 및 루프가 포함된 일반 Swift처럼 보이는 코드를 작성할 수 있게 했습니다.

Swift Evolution SE-0289에 따르면, result builder는 SwiftUI에 국한되지 않는 일반적인 메커니즘입니다. @ViewBuilder는 이 메커니즘의 구현 중 하나이며, 문자열 구성을 위한 @StringBuilder 및 다른 DSL을 위한 라이브러리 구현도 있습니다. SwiftUI에서 @ViewBuilder는 body뿐만 아니라 컨테이너(VStack, HStack, ZStack, List)의 클로저 매개변수에도 사용됩니다.

명령형 접근 방식과의 차이

명령형 UIKit에서는 UIView를 명시적으로 생성하고, 속성을 구성한 다음 addSubview를 통해 계층 구조에 추가합니다. @ViewBuilder를 사용하는 SwiftUI에서는 표시할 View를 선언적으로 설명하고, SwiftUI가 상태 변경에 따라 요소의 생성, 업데이트 및 제거를 관리합니다.

@ViewBuilder 작동 방식: result builder

Result builder는 정적 메서드 buildBlock, buildOptional, buildEither 등을 통해 표현식 시퀀스를 단일 복합 값으로 변환하는 Swift 메커니즘입니다. 컴파일러가 @ViewBuilder 어노테이션을 발견하면 컴파일 중에 자동으로 이러한 메서드를 코드 블록에 적용합니다.

swift
@resultBuilder
struct ViewBuilder {
    static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
    static func buildIf<C>(_ c: C?) -> C?
    static func buildEither<T, F>(first: T) -> ConditionalContent<T, F>
    static func buildEither<T, F>(second: F) -> ConditionalContent<T, F>
}

buildBlock은 1~10개의 표현식을 받아 TupleView를 반환합니다. 각 인자 수(표현식의 개수)에는 자체 buildBlock 오버로드가 있습니다: buildBlock부터 buildBlock까지. 이것이 하나의 @ViewBuilder 블록에서 요소 수가 10으로 제한된 이유입니다.

buildEither(first/second)는 if/else 구문을 처리합니다. 각 분기는 해당 메서드에 전달되고 결과는 ConditionalContent에 래핑됩니다. 이는 특정 분기 타입을 숨기고 SwiftUI에 통합 인터페이스를 제공하는 타입입니다.

@ViewBuilder의 암시적 동작

SwiftUI에서 body 프로퍼티는 이미 암시적으로 @ViewBuilder로 어노테이션되어 있습니다. 코드에서 해당 어노테이션을 볼 수는 없지만 컴파일러가 자동으로 적용합니다. 그러나 여러 View를 반환하는 사용자 정의 프로퍼티나 클로저 매개변수의 경우 어노테이션을 명시적으로 지정해야 합니다.

@ViewBuilder의 제한 사항 및 해결 방법

제한 사항 1 — 블록당 10개 요소. 이것은 @ViewBuilder의 가장 잘 알려진 제한 사항입니다. 같은 레벨에 10개 이상의 요소를 표시해야 하는 경우 컴파일러가 오류를 발생시킵니다. 해결 방법으로는 Group, ForEach, List 또는 하위 컴포넌트로 분할이 있습니다. Group은 시각적 중첩을 추가하지 않지만 각 Group은 하나의 요소로 계산됩니다.

swift
struct ManyElementsView: View {
    var body: some View {
        Group {
            Text("1"); Text("2"); Text("3")
            Text("4"); Text("5"); Text("6")
            Text("7"); Text("8"); Text("9")
        }
        Group {
            Text("10"); Text("11"); Text("12")
        }
    }
}

제한 사항 2 — 특정 구문 지원 부족. @ViewBuilder는 do/catch, guard, for-in(ForEach 없음) 및 기타 제어 흐름 구문을 지원하지 않습니다. 루프에는 식별 가능한 데이터와 함께 ForEach를 사용하세요. 오류 처리에는 Result나 옵셔널 값을 받는 별도의 View를 사용합니다.

제한 사항 3 — 디버깅 복잡성. @ViewBuilder에서 오류가 발생하면 컴파일러가 장황한 메시지를 생성하여 근본 원인을 찾기 어렵습니다. 일반적인 문제: if/else 분기의 타입 불일치, 10개 요소 제한 초과, 필요한 buildBlock 오버로드 부재 등이 있습니다.

@ViewBuilder 사용 패턴

패턴 1: if/else를 통한 조건부 표시. @ViewBuilder의 가장 일반적인 사용 사례입니다. 삼항 연산자나 팩토리 메서드 없이 상태에 따라 다른 View를 표시할 수 있습니다.

swift
struct StatusView: View {
    var status: LoadStatus

    @ViewBuilder
    var body: some View {
        switch status {
        case .loading:
            ProgressView("Loading...")
        case .loaded(let data):
            DataView(data: data)
        case .error(let message):
            ErrorView(message: message)
        }
    }
}

패턴 2: 함수 및 초기화 매개변수의 @ViewBuilder. 클로저를 통해 자식 View를 받는 재사용 가능한 컨테이너를 만드는 데 사용됩니다. 라이브러리 및 UI 컴포넌트의 표준 패턴입니다.

swift
struct SectionCard<Content: View>: View {
    let title: String
    @ViewBuilder let content: Content

    var body: some View {
        VStack(alignment: .leading) {
            Text(title).font(.headline)
            content
        }
        .padding()
        .background(Color.gray.opacity(0.1))
        .cornerRadius(12)
    }
}

패턴 3: ForEach와의 구성. @ViewBuilder는 ForEach와 올바르게 작동하여 데이터 배열에서 요소를 동적으로 생성할 수 있습니다. ForEach의 각 요소는 @ViewBuilder 컨텍스트에서 하나의 표현식으로 계산됩니다.

재사용 가능한 컴포넌트를 위한 사용자 정의 ViewBuilder 만들기

사용자 정의 ViewBuilder는 @ViewBuilder로 어노테이션되고 some View를 반환하는 사용자 정의 함수 또는 프로퍼티입니다. 이러한 함수를 통해 복잡한 표시 로직을 캡슐화하고 애플리케이션의 여러 부분에서 재사용할 수 있습니다.

swift
struct FormRow<Content: View>: View {
    let label: String
    @ViewBuilder let content: Content

    var body: some View {
        HStack {
            Text(label)
                .frame(width: 120, alignment: .trailing)
            content
        }
    }
}

// 사용 방법:
FormRow(label: "Name") {
    TextField("Enter name", text: $name)
}

FormRow(label: "Gender") {
    Picker("Select", selection: $gender) {
        Text("남성").tag(Gender.male)
        Text("여성").tag(Gender.female)
    }
}

중요한 규칙: @ViewBuilder를 사용하는 사용자 정의 함수는 구체적인 타입이나 View 프로토콜이 아닌 some View를 반환해야 합니다. 불투명 타입만이 구성의 유연성을 유지하면서 구체적인 구현을 숨길 수 있습니다.

성능: 사용자 정의 @ViewBuilder 함수는 직접 body 코드와 비교하여 오버헤드를 추가하지 않습니다. 컴파일러는 호출을 인라인화하고 결과 코드를 최적화합니다. body를 @ViewBuilder 함수로 분할하면 성능 저하 없이 가독성이 향상됩니다.

자주 묻는 질문

SwiftUI에서 @ViewBuilder란 무엇인가요?

@ViewBuilder는 여러 표현식과 조건이 포함된 코드 블록을 단일 View 타입으로 변환하는 result builder 어노테이션입니다. SwiftUI의 선언적 UI 내에서 익숙한 Swift 구문(if/else, switch, 옵셔널 표현식)을 사용할 수 있습니다.

@ViewBuilder에 10개 이상의 요소를 넣을 수 없는 이유는 무엇인가요?

제한은 buildBlock의 구현에서 비롯됩니다. 1부터 10까지 각 인자 수에 대해 메서드의 별도 오버로드가 존재합니다. Swift는 가변 제네릭을 지원하지 않으므로 오버로드 수가 고정되어 있습니다. 이를 해결하려면 Group, ForEach 또는 하위 컴포넌트를 사용하세요.

body 앞에 @ViewBuilder를 명시적으로 지정해야 하나요?

아니요, View 프로토콜은 body 프로퍼티에 @ViewBuilder를 암시적으로 적용합니다. 그러나 여러 View를 반환하는 사용자 정의 프로퍼티, 메서드 및 클로저 매개변수의 경우 어노테이션을 명시적으로 지정해야 합니다. 지정하지 않으면 컴파일러가 여러 표현식을 처리할 수 없습니다.

@ViewBuilder는 옵셔널 표현식을 어떻게 처리하나요?

옵셔널 표현식에는 buildIf 메서드가 사용되며, 옵셔널 View를 받아 값이 있으면 반환합니다. 값이 nil이면 buildIf는 nil을 반환하고 요소는 표시되지 않습니다. 이를 통해 body 내에서 if let을 사용할 수 있습니다.

@ViewBuilder를 switch와 함께 사용할 수 있나요?

네, Swift 5.9부터 @ViewBuilder는 buildExpression 메서드를 통해 switch를 지원합니다. 컴파일러는 각 case 분기를 해당 buildEither 호출로 변환합니다. switch 지원으로 중첩된 if/else 구문보다 코드를 더 읽기 쉽게 만듭니다.

요약

  • @ViewBuilder — SwiftUI에서 선언적으로 View 계층을 구축하기 위한 result builder
  • buildBlock은 표현식 시퀀스를 TupleView로 래핑(최대 10개 요소)
  • buildEither는 if/else 및 switch 분기를 위한 ConditionalContent 생성
  • buildIf는 옵셔널 표현식과 else 없는 if를 처리
  • Group과 ForEach는 블록당 10개 요소 제한을 우회하는 데 도움
  • 사용자 정의 @ViewBuilder 함수는 성능 저하 없이 재사용성 향상
  • @ViewBuilder는 body에 암시적으로 적용되지만 매개변수에는 명시적 어노테이션 필요

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기