正如马丁说,如果你看的文件VStack
的init(alignment:spacing:content:)
,你可以看到content:
参数具有的属性@ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
此属性引用ViewBuilder
类型,如果您查看生成的接口,则该类型类似于:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
该@_functionBuilder
属性是一个非官方功能的一部分,该功能称为“功能构建器”,在此处已针对Swift演变进行了介绍,并专门针对Xcode 11随附的Swift版本实现,从而可在SwiftUI中使用。
标记类型@_functionBuilder
允许将其用作各种声明(例如函数,计算属性以及在这种情况下为函数类型的参数)上的自定义属性。此类带注释的声明使用函数构建器来转换代码块:
- 对于带注释的函数,要转换的代码块是实现。
- 对于带注释的计算属性,要转换的代码块是getter。
- 对于带功能类型的带注释的参数,要转换的代码块是传递给它的任何闭包表达式(如果有)。
函数构建器转换代码的方式是由其构建器方法的实现定义的,例如buildBlock
采用一组表达式并将其合并为单个值。
例如,ViewBuilder
实现buildBlock
1到10个View
符合参数的实现,将多个视图合并为一个视图TupleView
:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
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 {
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")
}
Group {
Text("Hello world")
}
}
ViewBuilder
还实现其他功能构建器方法,例如:
extension ViewBuilder {
public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
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
明确)。
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder。