SwiftUI 리팩토링, @ViewBuilder

Ryan Cho·2025년 6월 26일

ViewModifier

View에 사용할 공통 modifiers만 분리할때 사용하는 프로토콜

struct TextFieldModifier: ViewModifier {
	func body(content: Content) -> some View {
    	content
        	.padding()
            .background()
            ... // 적용할 공통 modifier
    }
}

적용

.modifier() 으로 해당 공통 modifier를 적용

TextField("placeholder", text: $text)
	.modifier(TextFieldModifier())

@ViewBuilder

@ViewBuilder는 여러개의 View(단일)를 하나의 View처럼 조합할 수 있도록 해준다
예로, 버튼을 만든다고 생각해보자

Button {
	action함수
} label: {
	Text("버튼임")
    Image("버튼을 꾸며주는 이미지임")
}

이런식으로 label부분에 여러 View를 사용하지 않는가? 하나의 Button View인데말이다.
저 label: {} 이런식으로 작성된 문법은 클로저를 전달하는 @ViewBuilder 표현인것이다
해당 label의 타입은 다음과 같다

@ViewBuilder () -> Label

응용 및 리팩토링 1)

동일한 modifier를 사용하는 커스텀 버튼 뷰를 만든다고 가정하자.
action부분과, label에 해당하는 View만 다를것이라면 다음과 같이 만들 수 있다.

struct CustomButtonView<Content: View>: View {
	let action: () -> Void
    let label: Content
    
    init(action: @escaping () -> Void, @ViewBuilder label: () -> Content) {
    	self.action = action // 바로 실행될 함수가 아님 (클릭할때 실행)
        self.label = label()
    }
    
    var body: some View {
    	Button {
        	action()
        } label: {
        	label
            	.foregroundStyle()
                ...
        }
    }
}

@escaping은 클로저를 저장하고 나중에 쓰기 위함이다.

Content는 제네릭으로 만들어진 타입이고, View는 프로토콜이다.
외부에서 주입될 Content의 타입으로 받고(제네릭) View라는 프로토콜로 정해둔 것이다.


따라서 위 코드를 사용할때는 기본 Button View처럼 사용이 가능하다 (@ViewBuilder때문에)
(참고로 action은 클로저 함수이므로 원래 {} 문법으로 사용하고)

// 적용시
CustomButtonView {
	print("action!")
} label : {
	Text("버튼입니다")
}

응용 및 리팩토링 2)

좀 더 쉬운 예제일것이다. 만약 공통 modifier도 쓰고, 공통 View도 적절히 사용하는데 특정 부분의 View만 다를 경우에도 @ViewBuilder에 View 프로토콜을 가진 제네릭 타입을 반환하는 클로저 함수를 활용할 수 있다.

struct 배경이랑뭐하단뷰가동일한View<Content: View>: View {
	let content: Content
    
    init(@ViewBuilder content: () -> Content) {
    	self.content = content()
    }
    
    var body: some View {
    	ZStack {
        	...배경
            
            VStack {
            	content // 동적인 View
            }
            	.여러 공통 modifiers...
        }
    }
}

위 코드를 적용한다면 이번에도 역시 {} 으로 View를 담으면 된다.

var body: some View {
	배경이랑뭐하단뷰가동일한View {
    	Vstack { // 이 블록 안의 모든것이 content에 해당한다
        	여러 뷰들
        }
    }
}
profile
From frontend to fullstack

0개의 댓글