美文网首页
SwiftUI学习之@ViewBuilder

SwiftUI学习之@ViewBuilder

作者: 冷武橘 | 来源:发表于2023-08-03 22:02 被阅读0次

一、写在前面的例子

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)

相关文章

网友评论

      本文标题:SwiftUI学习之@ViewBuilder

      本文链接:https://www.haomeiwen.com/subject/nnbmpdtx.html