SwiftUI 如何使用 LazyVGrid 和 LazyHGrid 在网格中放置视图?

SwiftUI 的 LazyVGridLazyHGrid 为我们提供了相当多的灵活性的网格布局。 最简单的网格由三部分组成:原始数据,描述所需布局的 GridItem 数组以及将数据和布局组合在一起的 LazyVGridLazyHGrid

例如,这将使用大小为80点的像元创建垂直网格布局:

struct ContentView: View {
    let data = (1...100).map { "Item \($0)" }

    let columns = [
        GridItem(.adaptive(minimum: 80))
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

使用 GridItem(.adaptive(minimum:80) 意味着我们希望网格可以容纳每行尽可能多的项目,每个网格的最小大小为 80点。

如果要控制列数,可以改用 .flexible(),它还可以让您指定每个项目的大小,但现在可以控制有多少列。 例如,这将创建五列:

struct ContentView: View {
    let data = (1...100).map { "Item \($0)" }

    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

第三种选择是使用固定大小。 例如,这将使第一列的宽度恰好为 100 点,并允许第二列填充所有剩余空间:

struct ContentView: View {
    let data = (1...100).map { "Item \($0)" }

    let columns = [
        GridItem(.fixed(100)),
        GridItem(.flexible()),
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

您还可以使用 LazyHGrid 制作水平滚动的网格,除了在初始化程序中接受行之外,其工作方式几乎相同。

例如,我们可以创建 10 个并排的标题图像,它们像这样水平滚动:

struct ContentView: View {
    let items = 1...50

    let rows = [
        GridItem(.fixed(50)),
        GridItem(.fixed(50))
    ]

    var body: some View {
        ScrollView(.horizontal) {
            LazyHGrid(rows: rows, alignment: .center) {
                ForEach(items, id: \.self) { item in
                    Image(systemName: "\(item).circle.fill")
                        .font(.largeTitle)
                }
            }
            .frame(height: 150)
        }
    }
}

如您所见,创建水平和垂直网格所需的代码几乎相同,仅更改了 columnsrows

如果需要支持 iOS 13,则您将无权访问 LazyHGridLazyVGrid,因此请阅读以下内容以了解其他方法…

SwiftUI 为我们提供了用于垂直布局的 VStack 和用于水平布局的 HStack,但没有做到这两者–没有可以在网格结构中布局视图的东西。

幸运的是,我们可以利用 SwiftUI 的视图生成器系统来编写自己的代码。 这意味着编写必须使用行数和列数以及必须运行的闭包才能检索网格中给定单元格的视图的类型。 然后,可以在主体内部遍历所有行和列,并在 VStackHStack 内部创建单元格以形成网格,每次调用视图关闭以询问单元格中应包含的内容。

在代码中,它看起来像这样:

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

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

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

// An example view putting GridStack into practice.
struct ContentView: View {
    var body: some View {
        GridStack(rows: 4, columns: 4) { row, col in
            Image(systemName: "\(row * 4 + col).circle")
            Text("R\(row) C\(col)")
        }
    }
}

这将创建一个 4x4 网格,每个单元格中均包含图像和文本。