View에 사용할 공통 modifiers만 분리할때 사용하는 프로토콜
struct TextFieldModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background()
... // 적용할 공통 modifier
}
}
.modifier() 으로 해당 공통 modifier를 적용
TextField("placeholder", text: $text)
.modifier(TextFieldModifier())
@ViewBuilder는 여러개의 View(단일)를 하나의 View처럼 조합할 수 있도록 해준다
예로, 버튼을 만든다고 생각해보자
Button {
action함수
} label: {
Text("버튼임")
Image("버튼을 꾸며주는 이미지임")
}
이런식으로 label부분에 여러 View를 사용하지 않는가? 하나의 Button View인데말이다.
저 label: {} 이런식으로 작성된 문법은 클로저를 전달하는 @ViewBuilder 표현인것이다
해당 label의 타입은 다음과 같다
@ViewBuilder () -> Label
동일한 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("버튼입니다")
}
좀 더 쉬운 예제일것이다. 만약 공통 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에 해당한다
여러 뷰들
}
}
}