SwiftUI

SwiftUI ) ViewBuilder

Zedd0202 2021. 12. 5. 18:10
반응형

 

안녕하세요 :) Zedd입니다.

오늘은 ViewBuilder에 대해 공부!

 

# ViewBuilder

정의 : Closure에서 View를 구성하는 custom parameter attribute

뭔소린지 모르겠지만 "Closure에서 (Child) View를 구성한다"만 알면 된다. 

@ViewBuilder 사용법을 알아보자

 

# 사용 1. @ViewBuiler Prameter

HStack, VStack은 많이 쓰고 봤을것이다.

var body: some View {
    HStack {
        Text("Zedd")
        Text("Zedd")
    }
}


보통 이런식으로 쓸텐데, 저 HStack을 보면 Closure안에!!!!!! View들을 넣어주고 있다.

HStack의 생성자는 다음과 같이 생겼는데, 

    @inlinable public init(alignment: VerticalAlignment = .center,
                           spacing: CGFloat? = nil,
                           @ViewBuilder content: () -> Content)


@ViewBuilder content 파라미터(Closure타입)가 있는 것을 볼 수 있다.

다시 ViewBuilder의 정의를 보자.

Closure에서 View를 구성하는 custom parameter attribute

 

위 HStack 예제에서 봤듯이, @ViewBuilder 파라미터를 두어 View를 구성할 수 있는 나만의 View(?)를 만들 수 있다. 

struct ContentView: View {
         
    var body: some View {
        HStack {
            Text("Zedd")
            Text("Zedd")
        }
        .background(Color.purple)
    }
}

HStack이긴 HStack인데, 맨날 background color를 명시적으로 넣어줘야한다고 가정해보자. 

Color를 받아 자동으로 background를 넣어주는 HStack을 만들어보자.

struct ZeddHStack<Content>: View where Content: View {
    
    let content: () -> Content
    let color: Color
    
    init(color: Color = .clear,
         @ViewBuilder content: @escaping () -> Content) {
        self.color = color
        self.content = content
    }

    var body: some View {
        HStack {
            content()
        }
        .background(color)
    }
}

그럴 때 이렇게 @ViewBuilder 파라미터를 사용하여 child view를 생성할 수 있도록 해주면 된다. 

struct ContentView: View {
                
    var body: some View {
        ZeddHStack(color: .purple) {
            Text("Zedd")
            Text("Zedd")
        }
    }
}

이 방법은 공통적인 부모 Container를 만드는데 유용하게 쓸 수 있다. (HStack, VStack같이)

 

만약 Swift 5.4 이상을 사용하고, 딱히 생성자가 필요없다면

struct ZeddHStack<Content>: View where Content: View {
 
  @ViewBuilder let content: Content

  var body: some View {
      HStack {
          content
      }.background(Color.purple)
  }
}

생성자를 안만들고 content를 @ViewBuilder로 mark하여 사용해도 된다.

이렇게 @ViewBuilder파라미터를 사용하여 해당 Closure가 여러 Child View를 제공할 수 있도록 할 수 있다. 

 

# 사용 2.  @ViewBuilder computed property, Method

1번은 위와같이 파라미터 앞에 @ViewBuilder가 붙은식이라면, 

@ViewBuilder는 computed Property나 메소드 앞에도 붙을 수 있다. 

예를 들어보자. 

struct ContentView: View {
                
    var body: some View {
        HStack {
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
            Text("Zedd")
        }
    }
}

자 이런 간단한 코드가 있는데, Text("Zedd")가 body에 들어있어서 길어보임 && 얘를 따로 빼고 싶은 마음이 든다.

메소드로 빼고싶겠지만, 일단 Computed Property로 빼보자. 

struct ContentView: View {
                
    var body: some View {
        manyTexts
    }

    var manyTexts: some View {
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }
}

하지만 위 코드는 컴파일에러를 일으킨다.

그럼 한가지 궁금증이 생긴다.

struct ContentView: View {
                
    var body: some View { // ✅
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }

    var manyTexts: some View { // 🚨
        Text("Zedd") 
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }
}

🤔 : body랑 manyTexts랑 차이가 없는데? 왜  body에 넣으면 컴파일이 되고, 내가 만든 manyTexts는 컴파일에러를 발생시키지?

🧑‍💻 : 그 이유는

struct ContentView: View {
                
    @ViewBuilder // ✅ implicit 
    var body: some View {
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }
}

body 프로퍼티는 암시적으로 @ViewBuilder로 선언되어있기 때문. 

하지만 body외의 다른 프로퍼티나 메소드는

기본적으로 ViewBuilder로 유추(infer)하지 않기 때문에 @ViewBuilder를 명시적으로 넣어줘야한다. 

struct ContentView: View {
                
    var body: some View { 
        manyTexts
    }
    
    @ViewBuilder // ⬅️
    var manyTexts: some View { // ✅
        Text("Zedd") 
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }
}

이렇게.

메소드 역시 똑같다. 

struct ContentView: View {
                
    var body: some View {
        generateTexts()
    }

    @ViewBuilder // ⬅️
    func generateTexts() -> some View {
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
        Text("Zedd")
    }
}

 

 

반응형