似乎苹果公司的新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
之外使用它?
答案 0 :(得分:75)
As Martin says,如果您查看VStack
的init(alignment:spacing:content:)
的文档,则可以看到content:
参数具有属性@ViewBuilder
:< / p>
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
属性是名为“ function builders”的非官方功能的一部分,该功能已pitched on Swift evolution here并专门针对Xcode 11随附的Swift版本实现。在SwiftUI中使用。
标记类型@_functionBuilder
可以将其用作各种声明(例如函数,计算属性以及在这种情况下为函数类型的参数)上的自定义属性。此类带注释的声明使用函数生成器来转换代码块:
函数构建器转换代码的方式由其builder methods的实现(例如buildBlock
)定义,该实现采用一组表达式并将它们合并为一个值。
例如,ViewBuilder
为1到10个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!"))
}
}
}
opaque result type 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")
)
}
}
(为清楚起见,向ViewBuilder.buildBlock
发送冗余的1个参数的调用)。
答案 1 :(得分:4)
在What's New in Swift WWDC video中有关DSL的部分(从〜31:15开始)中描述了类似的内容。该属性由编译器解释并转换为相关代码: