首先,当你见到code里使用到它,或者你想使用它的时候。我们大概率可以推断出,基本的SwiftUI元素和组件已经没办法满足我们项目复杂需求或者定制化的要求了,我们要DIY一下扩展性更强的View。
今天我们来好好捋捋这个@ViewBuilder
import SwiftUI
struct ContentView: View {
var body: some View {
HStack{
Text("hello")
Text("world")
}
}
}
很简单的hello world级别的SwiftUI代码对吧。首先,在SwiftUI的世界里,万物都是View,但你有没有相过,为什么我们在HStack这个View里放了两个Text View,它们就能够水平对齐呢,为什么Hstack可以接受两个View呢?我们点击HStack的definition里看看:
@inlinable public init(
alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil,
@ViewBuilder content: () -> Content
)
从源码中我们可看到,init方法中有一个被@ViewBuilder修饰的闭包content,这意味着这个闭包内部的表达式需要由@ViewBuilder处理。它怎么处理呢,Swift在编译被@ViewBuilder修饰的闭包的时候,会先尝试查找@ViewBuilder结构中的静态buildBlock方法,这个方法有两个View作为参数,我们先来看一下:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
}
从这个ViewBuilder的源码中我们可以看到,它接收两个View作为输入参数,返回一个联合了两个View的TupleView。
你往下阅读源码,会发现还有其他的声明:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
这告诉我们,在目前的SwiftUI版本下,@ViewBuilder修饰的闭包内只能接受十个View.
什么叫做TupleView呢?
TupleView是根据视图值的快速元组创建的视图。 TupleView内部没有任何逻辑。 它只保留视图。 TupleView完全透明,其行为类似于其父视图。 这意味着当你将其放入HStack中时,TupleView会将来自元组的视图放置在水平方向上。
OK,现在我们已经明白了@ViewBuilder的作用,我们来想想怎么使用它。
假设我们要给我们的app做notification功能,首先我们想到这个notification应该组件化,但我们notification的内容应该是多样化的,定制化的。这时候,我们可以使用@ViewBuilder
import SwiftUI
struct NotificationView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.padding()
.background(Color(.tertiarySystemBackground))
.cornerRadius(16)
.transition(.move(edge: .top))
.animation(.spring())
}
}
我们在这里写了一个NotificationView。在这个View里,我们绘制出了notification基本的样式,但是content的类型是一个被@ViewBuilder修饰的闭包。这就给了我们后面自定义推送内容打下了基础。
当我们想使用这个NotificationView时,我们可以这样:
import SwiftUI
struct ContentView: View {
@State private var notificationShown = false
var body: some View {
VStack {
if self.notificationShown {
NotificationView {
Text("notification")
}
}
Spacer()
Button("toggle") {
self.notificationShown.toggle()
}
Spacer()
}
}
}