当前位置: 首页 > 知识库问答 >
问题:

什么支持SwiftUI的DSL?

幸阳波
2023-03-14

苹果新的SwiftUI框架似乎使用了一种新的语法,可以有效地构建元组,但有另一种语法:

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

试图解决这个语法到底是什么,我发现这里使用的VStack初始化器需要类型()-

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

    return test
}

这样,test显示自己属于VStack类型

问题

现在我想知道这个例子中的两个Text实例是如何转换为TupleView的


共有2个答案

殳俊
2023-03-14

Swift WWDC视频中关于DSL的部分(从~31:15开始)中描述了一个类似的事情。该属性由编译器解释并转换为相关代码:

凌钊
2023-03-14
匿名用户

正如Martin所说,如果您查看VStackinit(对齐:间距:内容:)的文档,您可以看到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中使用。

标记类型@\u functionBuilder允许将其用作各种声明的自定义属性,例如函数、计算属性,在本例中还包括函数类型的参数。此类带注释的声明使用函数生成器转换代码块:

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

函数生成器转换代码的方式是通过实现生成器方法来定义的,如BuildBlock,它接受一组表达式并将它们合并成一个值。

例如,ViewBuilder为1到10个View一致性参数执行buildBlock,将多个视图合并为一个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!"))
    }
  }
}

导致不透明的结果类型某些ViewTupleView所满足

您会注意到,ViewBuilder最多只定义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多个静态定义的视图,则可以使用视图轻松解决此限制:

  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参数调用)。

 类似资料:
  • 本文向大家介绍为什么Java不支持<<相关面试题,主要包含被问及为什么Java不支持<<时的应答技巧和注意事项,需要的朋友参考一下 Java添加了运算符“ >>>”来执行逻辑右移,但是由于 逻辑和算术左移运算是相同的 ,因此Java中没有“ <<<”运算符。 来自Java的Shifts …

  • 问题内容: 我开始使用Node.js,并且很难确定节点支持哪种JavaScript版本,这使得很难确定可以使用哪些功能。这就是我所知道的。 节点使用V8 V8实现了ECMA-262,第3版中指定的ECMAScript ECMA-262,第三版是JavaScript 1.5 鉴于此,我假设我可以在节点中使用JavaScript 1.5兼容代码。但是,事实证明我可以使用,以及其他结构,即使根据MDC,

  • 问题内容: 我正在学习SwiftUI。我遇到了“ GeometryReader”。我想知道为什么以及何时使用它? 问题答案: 更新 自从发布答案以来,我还写了一篇有关GeometryReader如何工作的文章。 查看它以获取更详细的说明:https : //swiftui-lab.com/geometryreader-to-the-rescue/ GeometryReader是一个视图,使您可以访

  • 我注意到一件奇怪的事情,显然Firefox说它支持HTTPS上的Brotli,但不支持HTTP?Brotli与gzip类似,但效率更高,为什么它会将其限制为HTTPS?在HTTPS选项卡上,我看到: 他被派去了。但在另一个网站上,我没有看到。为什么它不能做Brotli压缩HTTP?

  • 现支持语言:中文简体,中文繁体,英文。 在安装服务器端程序时,会有选项,可以根据自己的需要,选择语言。

  • 问题内容: 苹果的新框架似乎使用了一种 新型语法 ,可以有效地构建元组,但又具有另一种语法: 尝试解决这种语法的实际含义时 ,我发现这里使用的初始化程序将类型的闭包 作为第二个参数,其中的通用参数是通过闭包推断的。为了找出要推断的类型,我对代码进行了一些更改,并保持其功能: 以此,表明自己是类型,即类型。向上看,我发现它是一个源自自身的包装器类型,只能通过传递应该包装的元组来进行初始化。 题 现在