ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SwiftUI] - @ViewBuilder
    iOS/SWIFTUI 2024. 7. 16. 22:29
    728x90
    반응형

    오랜만이다. 

    요즘 프로젝트를 하면서 뷰 관련코드를 최적화 하려고 노력중이다.

    뷰의 재사용성을 높이기 위해 연구를 하던 중 @ViewBuilder라는 키워드를 알게 되었다.

    사실 우리도 모르게 우리는 ViewBuilder를 사용하고 있었는데, 과연 이 녀석은 무슨 녀석일까?

     

    @ViewBuilder

    일반적으로 ViewBuilderclosure를 통해 여러 자식 뷰를 제공할때 사용할 수 있다. 예를 들자면, contextMenu 함수는 ViewBuilder를 통해 하나 이상의 뷰를 생성하는 closure를 허용한다.

     

     

    @ViewBuilderSwiftUI에서 사용되는 특수한 속성 래퍼(attribute wrapper)로, 여러 뷰를 하나의 클로저로 묶어서 반환할 수 있게 해주며 이를 여러 개의 뷰를 조합해 하나의 단일 뷰처럼 사용할 수 있도록 한다.

     

    위는 @ViewBuilder의 정의들이다. 쉽게 말해 그냥 여러 뷰를 클로져로 반환한다는 말인데, 우리가 사용하는 V/H/ZStack, ForEach 등 클로져로 안에 뷰를 담을 수 있는 컴포넌트들은 모두 이 ViewBuilder 속성으로 선언 되어있다.

     

    그럼 Button도 클로져로 사용할 수 있는데 이것도 ViewBuilder속성으로 선언될까?

     

    결론부터 얘기하면 맞기도 하고 아니기도 하다.

     

    Button() 컴포넌트는 다음과 같이 초기화 되어있다.

    @ViewBuilder 속성은 클로져에 뷰를 정의할 수 있도록 해주는 속성이다. label 파라미터의 경우 개발자가 직접 뷰를 정의할 수 있지만, action의 경우 뷰를 파라미터로 받는 것이 아닌 명령어나 메서드 등을 파라미터로 받는다.

     

    이쯤 되면 이해가 대충갈 것이다.

     

    그럼 예제를 만들어 보자

    struct RainbowTextForEach<Content>: View where Content: View {
        
        let content: () -> Content
        let rainbow:[Color] = [.red,.orange,.yellow,.green,.blue,.indigo,.purple]
        
        init(@ViewBuilder content: @escaping () -> Content) {
            self.content = content
        }
    
        var body: some View {
            ForEach(rainbow,id: \.self){ color in
                content()
                    .foregroundColor(color)
            }
        }
        
    }

    클로져에 뷰 코드를 삽입하면 그 뷰들을 모두 무지개 색 아이템으로 변형해 출력하는 컴포넌트를 만들었다.

     

    하나씩 살펴 보자면

    struct RainbowTextForEach<Content>: View where Content: View

    이 컴포넌트는 View 타입의 파라미터를 제네릭으로 받는다. 여기서 Content는 타입 파라미터로 받는다.

    struct RainbowTextForEach<Content:View>: View

    이렇게도 수정할 수 있다.

     

    그리고

     let content: () -> Content

    타입 파라미터를 반환하는 클로져를 생성해주고

     

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    초기화를 해주는데, 여기서 content를 @escaping 클로져로 초기화하는 이유는 뷰의 생명주기와 관련이 있다. 만약 escaping이 아니라면 이 컴포넌트가 동작하는 중에는 다른 코드는 동작하지 않을 것이다. 

     

    그래서 이 부분처럼 

    ForEach(rainbow,id: \.self){ color in
        content()
            .foregroundColor(color)
    }

    content를 ForEach스코프에서 기존에 준비 되었던 색깔 배열 수 만큼 돌려 무지개 뷰를 만들 수 있다.

     

    RainbowTextForEach{
            VStack{
                Text("안녕하세요")
                RoundedRectangle(cornerRadius: 10)
            }
    }

     

     

     

    @ViewBuilder의 역할은 여기서 끝난게 아니다.

    개발하다가 이런 에러를 본적이 있을 것이다. (아님 말구..)

     

    기존에 이걸 해결하려면 Z/H/VStack이나 뭐 Group,ForEach등등 스코프에 삽입했어야 했을 것이다. 하지만 우리는 이제 알 수 있다. 방금 언급한 것들 모두 @ViewBuilder속성으로 정의 되어있다.

     

    그럼 얘네도 그렇게 정의하면..?

    에러가 사라진다..!

     

    "이렇게 굳이 할필요가 있나?" 라고 생각한다면 이것을 보아라

     

     

    왼쪽은 hello메서드를 VStack안에 삽입, 오른쪽은 HStack에 삽입했을 때 나오는 결과다.

    뷰 메서드를 @ViewBuilder로 선언할 경우 이렇게 유동적으로 뷰를 정의할 수 있게 된다.

     

    'iOS > SWIFTUI' 카테고리의 다른 글

    [SwiftUI] - GeometryReader  (0) 2024.07.26
    [SwiftUI] #문법 - @ObservedObject  (0) 2023.04.14
    [SWIFTUI] #문법 - @Binding  (1) 2023.01.04
    [SWIFTUI] #문법 - @State  (0) 2022.10.09
Designed by Tistory.