正如 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
实现buildBlock
1 到 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
)。