SwiftUI의 DSL을 활성화하는 것은 무엇입니까?


88

Apple의 새로운 SwiftUI프레임 워크는 효과적으로 튜플을 빌드하는 새로운 종류의 구문 을 사용 하지만 다른 구문이있는 것 같습니다.

var body: some View {
    VStack(alignment: .leading) {
        Text("Hello, World") // No comma, no separator ?!
        Text("Hello World!")
    }
}

이 구문이 실제로 무엇인지 다루려고 시도하면서VStack 여기에 사용 된 이니셜 라이저가 유형의 클로저를 () -> Content 두 번째 매개 변수로 취 한다는 것을 알았습니다. 여기에서 클로저를 통해 추론 Content되는 일반 매개 변수는 이에 부합합니다 View. 어떤 유형 Content이 추론 되는지 알아보기 위해 기능을 유지하면서 코드를 약간 변경했습니다.

var body: some View {
    let test = VStack(alignment: .leading) {
        Text("Hello, World")
        Text("Hello World!")
    }

    return test
}

이와 함께, test유형으로 자신을 밝혀 VStack<TupleView<(Text, Text)>>그 의미 Content유형입니다 TupleView<Text, Text>. 를 찾아 보면 TupleView, SwiftUI래핑해야하는 튜플을 전달해야만 초기화 할 수있는 자체 에서 시작된 래퍼 유형이라는 것을 알았습니다 .

질문

이제이 Text예제 의 두 인스턴스가 어떻게 TupleView<(Text, Text)>. 이 해킹되어 SwiftUI있으므로 잘못된 정규 스위프트 구문? 유형이 TupleView되는 것은 SwiftUI이 가정을 뒷받침합니다. 아니면 이것이 유효한 Swift 구문입니까? 그렇다면 외부 에서 어떻게 사용할 수 있습니까?SwiftUI



답변:


108

마틴 말했듯이 당신에 대한 설명서를 보면, VStackinit(alignment:spacing:content:), 당신은 것을 볼 수 있습니다 content:매개 변수는 속성이 있습니다 @ViewBuilder:

init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
     @ViewBuilder content: () -> Content)

이 속성은 ViewBuilder생성 된 인터페이스를 보면 다음과 같은 유형을 참조합니다 .

@_functionBuilder public struct ViewBuilder {

    /// Builds an empty view from an block containing no statements, `{ }`.
    public static func buildBlock() -> EmptyView

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock(_ content: Content) -> Content 
      where Content : View
}

@_functionBuilder속성은 " 함수 빌더 " 라는 비공식적 기능의 일부로 , 여기에서 Swift의 진화에 대해 언급했으며 Xcode 11과 함께 제공되는 Swift 버전을 위해 특별히 구현되어 SwiftUI에서 사용할 수 있습니다.

유형을 표시하면 @_functionBuilder함수, 계산 된 속성 및이 경우 함수 유형의 매개 변수와 같은 다양한 선언에서 사용자 정의 속성으로 사용할 수 있습니다. 이러한 주석이 달린 선언은 함수 빌더를 사용하여 코드 블록을 변환합니다.

  • 주석이 달린 함수의 경우 변환되는 코드 블록이 구현입니다.
  • 주석이 달린 계산 된 속성의 경우 변환되는 코드 블록이 게터입니다.
  • 함수 유형의 주석이 달린 매개 변수의 경우 변환되는 코드 블록은 전달되는 클로저 표현식입니다 (있는 경우).

함수 빌더가 코드를 변환하는 방식은 일련의 표현식을 가져와 단일 값으로 통합하는와 같은 빌더 메소드 의 구현에 의해 정의됩니다 buildBlock.

예를 들어 1 ~ 10 개의 준수 매개 변수 를 ViewBuilder구현 buildBlock하여 View여러 뷰를 단일으로 통합합니다 TupleView.

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock<Content>(_ content: Content)
       -> Content where Content : View

    public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) 
      -> TupleView<(C0, C1)> where C0 : View, C1 : View

    public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2)
      -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View

    // ...
}

이를 통해 VStack의 이니셜 라이저에 전달 된 클로저 내의 뷰 표현식 세트를 buildBlock동일한 수의 인수 를 사용하는 호출로 변환 할 수 있습니다 . 예를 들면 :

struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Hello World!")
    }
  }
}

에 대한 호출로 변환됩니다 buildBlock(_:_:).

struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
    }
  }
}

결과 은폐하는 결과 유형 some View 에 의해 충족되고 TupleView<(Text, Text)>.

당신은 점에 유의하겠습니다 ViewBuilder만 정의 buildBlock우리가 11 파단을 정의하려고 시도 그렇다면, 10 개의 매개 변수를 :

  var body: some View {
    // error: Static member 'leading' cannot be used on instance of
    // type 'HorizontalAlignment'
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
    }
  }

이 코드 블록을 처리 할 빌더 메서드가 없기 때문에 컴파일러 오류가 발생합니다 (이 기능은 아직 작업 중이므로 관련 오류 메시지는 그다지 도움이되지 않습니다).

실제로는 사람들이이 제한에 자주 부딪 힐 것이라고 생각하지 않습니다. 예를 들어 위의 예는 ForEach대신 뷰를 사용하여 더 잘 제공 됩니다.

  var body: some View {
    VStack(alignment: .leading) {
      ForEach(0 ..< 20) { i in
        Text("Hello world \(i)")
      }
    }
  }

그러나 정적으로 정의 된 뷰가 10 개 이상 필요한 경우 뷰를 사용하여이 제한을 쉽게 해결할 수 있습니다 Group.

  var body: some View {
    VStack(alignment: .leading) {
      Group {
        Text("Hello world")
        // ...
        // up to 10 views
      }
      Group {
        Text("Hello world")
        // ...
        // up to 10 more views
      }
      // ...
    }

ViewBuilder 또한 다음과 같은 다른 함수 작성기 메서드를 구현합니다.

extension ViewBuilder {
    /// Provides support for "if" statements in multi-statement closures, producing
    /// ConditionalContent for the "then" branch.
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View

    /// Provides support for "if-else" statements in multi-statement closures, 
    /// producing ConditionalContent for the "else" branch.
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View
}

이를 통해 if 문을 처리 할 수 ​​있습니다.

  var body: some View {
    VStack(alignment: .leading) {
      if .random() {
        Text("Hello World!")
      } else {
        Text("Goodbye World!")
      }
      Text("Something else")
    }
  }

다음으로 변환됩니다.

  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(
        .random() ? ViewBuilder.buildEither(first: Text("Hello World!"))
                  : ViewBuilder.buildEither(second: Text("Goodbye World!")),
        Text("Something else")
      )
    }
  }

( ViewBuilder.buildBlock명확성 을 위해 중복 1 인수 호출을 냄).


3
ViewBuilderbuildBlock최대 10 개의 매개 변수 만 정의 합니다. 즉, var body: some View11 개 이상의 하위보기를 가질 수 없다는 의미 입니까?
LinusGeffarth

1
@LinusGeffarth 실제로 사람들이 ForEach보기 와 같은 것을 대신 사용하기를 원할 가능성이 높기 때문에 사람들이이 제한에 자주 부딪 힐 것이라고 생각하지 않습니다 . 그러나이 Group제한을 해결 하기 위해 보기를 사용할 수 있으며 ,이를 표시하기 위해 내 대답을 편집했습니다.
Hamish

3
@MandisaW-보기를 자신의보기로 그룹화하고 재사용 할 수 있습니다. 나는 그것에 문제가 없다고 생각합니다. 저는 사실 지금 WWDC에 있고 SwiftUI 연구소의 엔지니어 중 한 명과 이야기를 나눴습니다. 그는 현재 Swift의 한계라고 말했고, 그들은 현명한 숫자로 10을 사용했습니다. 가변 제네릭이 Swift에 도입되면 원하는만큼 "서브 뷰"를 가질 수 있습니다.
Losiowaty

1
아마도 더 흥미로울 것입니다. buildEither 메소드의 요점은 무엇입니까? 둘 다 구현해야 할 것 같고 둘 다 동일한 반환 유형을 가지고 있습니다. 각각 문제의 유형을 반환하지 않는 이유는 무엇입니까?
Gusutafu

1
ASTPrinter 버그에 대한 내 의견에 이어 함수 빌더 PR이 병합되면 마스터에서 수정됩니다 .
Hamish

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.