100

似乎 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并因此无效的常规 Swift 语法? TupleView作为一个SwiftUI类型支持这个假设。或者这是有效的 Swift 语法?如果是的话,如何在外面使用它SwiftUI

4

2 回答 2

122

正如 Martin 所说,如果您查看VStack's的文档init(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
}

该属性是名为“ function builders@_functionBuilder ”的非官方功能的一部分,该功能已在此处介绍 Swift 进化,并专门为 Xcode 11 附带的 Swift 版本实现,允许在 SwiftUI 中使用。

标记类型@_functionBuilder允许它用作各种声明的自定义属性,例如函数、计算属性,在这种情况下,还包括函数类型的参数。此类带注释的声明使用函数构建器来转换代码块:

  • 对于带注释的函数,被转换的代码块就是实现。
  • 对于带注释的计算属性,被转换的代码块是 getter。
  • 对于函数类型的注释参数,被转换的代码块是传递给它的任何闭包表达式(如果有的话)。

函数构建器转换代码的方式由其构建器方法的实现定义,例如buildBlock,它采用一组表达式并将它们合并为单个值。

例如,ViewBuilder实现buildBlock1 到 10 个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了 10 个参数,所以如果我们尝试定义 11 个子视图:

  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")
      )
    }
  }

(为了清楚起见,发出多余的 1 参数调用ViewBuilder.buildBlock)。

于 2019-06-03T22:08:22.520 回答
14

What's New in Swift WWDC 视频中关于 DSL 的部分描述了类似的事情(从 ~31:15 开始)。该属性由编译器解释并翻译成相关代码:

在此处输入图像描述

于 2019-06-06T08:59:32.580 回答