[SwiftUI 知识碎片] Debris-16 自定义容器

324 阅读3分钟
译自 Custom containers
更多内容欢迎关注公众号 「Swift花园」

尽管不会很常用,我想向你展示,在 SwiftUI app 中创建自定义视图是完全可行的。这需要用到更高级的 Swift 知识,因为它利用了Swift 的一些强大的特性。

小试牛刀,我们将创建一个新的 stack 类型,它叫 GridStack,可以让我们以网格的形式创建任意多的视图。我们要声明一个叫GridStack的遵循View 协议的结构体,它有行和宽的数字,在网格内有许多内容单元,这些单元本身也要遵循View 协议。

在 Swift 中我们这样写:

struct GridStack<Content: View>: View {
    let rows: Int
    let columns: Int
    let content: (Int, Int) -> Content

    var body: some View {
        // more to come
    }
}

第一行 —— struct GridStack<Content: View>: View —— 用一个 Swift 的高级特别叫 泛型,在这里它的意思是 “你可以提供任意类型的内容,但它必须遵循 View 协议。” 在冒号之后,我们又加了一个 View ,声明 GridStack 本身也遵循View 协议。

特别注意一下 let content 这行 —— 它定义了一个闭包 —— 必须接收两个整数,并且返回某种我们可以显示的内容。

我们需要完成 body 属性 —— 通过组合多个 vertical 和 horizontal 的 stack 来按要求创建许多单元。我们不需要说明每个单元里有什么,因为我们可以通过合适的行号和列号来调用 content 闭包,

因此,我们这样填充代码:

var body: some View {
    VStack {
        ForEach(0 ..< rows) { row in
            HStack {
                ForEach(0 ..< self.columns) { column in
                    self.content(row, column)
                }
            }
        }
    }
}

现在我们拥有了一个自定义容易,我们可以用它写一个视图,像下面这样:

struct ContentView: View {
    var body: some View {
        GridStack(rows: 4, columns: 4) { row, col in
            Text("R\(row) C\(col)")
        }
    }
}

我们的 GridStack可以接收各种类型的单元类型,只要它们遵循View协议。因此,如果我们愿意,可以给单元创建自己的 stack 。

GridStack(rows: 4, columns: 4) { row, col in
    HStack {
        Image(systemName: "\(row * 4 + col).circle")
        Text("R\(row) C\(col)")
    }
}

更进一步

如果想获取更多的弹性,我们还可以用 SwiftUI 的一个特性,叫 view builder ,它允许我们传入多个视图,让 view builder 隐式地为我们创建 stack 。

为了使用 view builder ,我们需要给GridStack 结构体创建自定义的构造器,因此我们用 SwiftUI 的 view builder 系统来标记 content 闭包:

init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
    self.rows = rows
    self.columns = columns
    self.content = content
}

这个过程基本上是把结构体的属性复制到构造器中作为参数,不过要留意 @ViewBuilder 特性。你还看到 @escaping 特性,它允许我们存储闭包,以便稍后使用。

有了上面的代码,SwiftUI 现在可以为单元自动地创建一个 horizontal stack:

GridStack(rows: 4, columns: 4) { row, col in
    Image(systemName: "\(row * 4 + col).circle")
    Text("R\(row) C\(col)")
}

两种选项都可以工作,所以按你喜欢的来吧。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~