一、写在前面的例子
struct ContentView: View {
var body: some View {
VStack{
RedBackgroundAndCornerView.makeLabel(content: Text("text1"))
RedBackgroundAndCornerView.makeLabel(content: Text("text2"))
RedBackgroundAndCornerView.makeLabel(content: Text("text3"))
}
}
}
struct RedBackgroundAndCornerView<Content:View>{
static func makeLabel(content:Content) -> some View {
content.foregroundColor(.red)
}
}
上面的代码是传递一个Text,并设置textColor为红色,虽然没有毛病但是在SwiftUI中使用这样的函数调用感觉怪怪的,显得格格不入,然后我们用extension 得到这样,看起来好多了。
extension View{
func makeLabel() -> some View {
self.foregroundColor(.red)
}
}
struct ContentView: View {
@State var needHidden: Bool = false
var body: some View {
VStack{
Text("text2").makeLabel()
Text("text3").makeLabel()
Text("text1").makeLabel()
}
}
}
其实我们还可以尝试这样,将Text通过闭包传递过去
struct ContentView: View {
var body: some View {
RedBackgroundAndCornerView {
Text("333")
}
}
}
struct RedBackgroundAndCornerView<Content:View>: View{
let content: Content
init(content: () -> Content) {
self.content = content()
}
var body: some View {
content
.background(Color.red)
.cornerRadius(5)
}
}
上面我们看到了VStack和HStack的感觉,尝试这样
RedBackgroundAndCornerView {
Text("333")
Text("4444333")
}
很明显编译器会报错,普通的闭包不接受我们这样。
我们看一下Hstack,如下。
init(
alignment: HorizontalAlignment = .center,
spacing: CGFloat? = nil,
pinnedViews: PinnedScrollableViews = .init(), @ViewBuilder content: () -> Content
)
对比我们的闭包和它闭包不一样的地方,它有个@ViewBuilder 。
二、@ViewBuilder
通常使用ViewBuilder作为生成子视图的闭包参数的参数属性,允许这些闭包提供多个子视图。例如,下面的contextMenu函数接受一个闭包,该闭包通过视图构建器生成一个或多个视图。
func contextMenu<MenuItems: View>(
@ViewBuilder menuItems: () -> MenuItems
) -> some View
这个函数的客户端可以使用多语句闭包来提供几个子视图,如下面的例子所示:
myView.contextMenu {
Text("Cut")
Text("Copy")
Text("Paste")
if isSymbol {
Text("Jump to Definition")
}
}
如上那样我们上面的例子只需要把@ViewBuilder 加上,就可以在闭包里写多个视图了
struct ContentView: View {
var body: some View {
RedBackgroundAndCornerView {
Text("333")
Text("4444333")
}
}
}
struct RedBackgroundAndCornerView<Content:View>: View{
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack{
content
.background(Color.red)
.cornerRadius(5)
}
}
}
在swift 5.1 的另一个新特性 Funtion builders。如果你实际观察 vstack 这个初始化方法的签名,会发现 content 前面其实有一个@vieweuilder标记,而 Vieweuilder 则是一个由@functionBuilder 进行标记的struct。
1 @_functionBuilder public struct ViewBuilder
1 @_functionBuilder public struct ViewBuilder { /* */ }
使用 @functioneuilder 进行标记的类型(这里的 Vieweuilder),可以被用来对其他内容进行标记(这里用evieweuilder 对content 进行标记)。被用 function builder 标记过的viewBuilder 标记以后,content 这个输入的function 在被使用前,会按照 ViewBuilder 中合适的 buildB1ock 进行 build 后再使用。如果你阅读 ViewBuilder 的文档,会发现有很多接受不同个数参数的 buildB1ock 方法,它们将负责把闭包中一一列举的Text 和其他可能的view 转换为一个Tupleview并返回。由此,content 的签名0-二content 可以得到满足。实际上构建这个 vstack 的代码会被转换为类似下面这样的等效伪代码(不能实际编译)。
VStack(alignment: leading)
{ viewBuilder -> Content in
let text1 = Text("Turtle Rock"). font(. title)
let text2 = Text(" Joshua Tree National Park"). font(.subheadline)
return viewBuilder .buildBlock(text1, text2)