什么使SwiftUI的DSL成为可能?

苹果的新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类型支持这种假设。还是这个

如果是,请问如何

回答:

正如马丁说,如果你看的文件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允许将其用作各种声明(例如函数,计算属性以及在这种情况下为函数类型的参数)上的自定义属性。此类带注释的声明使用函数构建器来转换代码块:

  • 对于带注释的功能,要转换的代码块是实现。
  • 对于带注释的计算属性,要转换的代码块是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!"))

}

}

}

导致不透明的结果类型 someView由满足TupleView<(Text, Text)>

您会注意到,最多ViewBuilder只能定义buildBlock10个参数,因此,如果我们尝试定义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明确说明)。

以上是 什么使SwiftUI的DSL成为可能? 的全部内容, 来源链接: utcz.com/qa/403007.html

回到顶部