LazyVGrid & LazyHGrid

List 视图是显示滚动数据行的好方法,但有时还需要数据列,能够在大屏上显示更多数据。这时就要用到 LazyHGridLazyVGrid


1. 用 GridItem 定义布局

定义网格布局有以下几种常用方法:

// 1.创建了3列网格,并让每一列具备精确宽度
let columns = [
    GridItem(.fixed(80)),
    GridItem(.fixed(80)),
    GridItem(.fixed(80))
]

// 2.控制每个单元格最小宽度,在此基础上网格在每行中容纳尽可能多的项目,所以列数不定,只写一个即可
let columns = [
    GridItem(.adaptive(minimum: 80))
]

// 3.使用 .flexible 精确控制列数(4列,那里面的单元格大小可以自适应)
let columns = [
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible())
]

// 4.结合起来使用:这将使第一列的宽度恰好为 100,第二列则会填充所有剩余空间
let columns = [
    GridItem(.fixed(100)),
    GridItem(.flexible()),
]

<aside> 💡 .flexible 用于自动调整列的宽度的大小,而 .adaptive 用于根据可用空间动态调整列的数量。根据具体的布局需求,你可以选择使用这两个选项中的一个来定义网格布局中的列。

</aside>

2. 用 LazyV/HGrid 创建视图

定义好布局后,应该将网格以及所需数量的项目放置在 ScrollView 内。您在网格中创建的每个项目都会自动分配一列,就像列表中的行自动放置在其父项中一样。我们只定义 LazyHGridLazyVGrid 两者中的一个,具体取决于想要哪种类型的网格。

// 定义垂直网格 LazyVGrid 时,参数是 columns
LazyVGrid(columns: layout, spacing: 20) {
		ForEach(0..<100) {
				Text("Item \\($0)")
		}
}

// 定义水平网格 LazyHGrid 时,参数是 rows
LazyHGrid(rows: layout, alignment: .center) {
		ForEach(0..<100) {
				Text("Item \\($0)")
		}
}

3. 放入滚动视图

ScrollView {
    LazyVGrid(columns: layout) {
        ForEach(0..<1000) {
            Text("Item \\($0)")
        }
    }
}

制作水平网格的过程几乎相同,您只需使 ScrollView 水平工作,然后使用行而不是列创建 LazyHGrid

ScrollView(.horizontal) {
    LazyHGrid(rows: layout) {
        ForEach(0..<1000) {
            Text("Item \\($0)")
        }
    }
}

Grid & GridRow

Grid 视图可以创建静态视图网格,并精确控制每行和每列的内容。您可以使用 GridRow 标记各个行,然后还可以配置每个单元格的宽度。

// 作为基本示例,这将创建一个 2x2 网格,其中的文本反映每个单元格的位置:
Grid {
    GridRow {
        Text("Top Leading")
            .background(.red)

        Text("Top Trailing")
            .background(.orange)
    }

    GridRow {
        Text("Bottom Leading")
            .background(.green)

        Text("Bottom Trailing")
            .background(.blue)
    }
}

如果您不想每行具有相同数量的单元格,有3种选择:

// 第1种方法:什么都不做,SwiftUI 将自动插入空单元格以确保 rows 相等
struct ContentView: View {
    @State private var redScore = 0
    @State private var blueScore = 0
    var body: some View {
        Grid {
            GridRow {
                Text("Red")
                ForEach(0..<redScore, id: \\.self) { _ in
                    Rectangle()
                        .fill(.red)
                        .frame(width: 20, height: 20)
                }
            }

            GridRow {
                Text("Blue")
                ForEach(0..<blueScore, id: \\.self) { _ in
                    Rectangle()
                        .fill(.blue)
                        .frame(width: 20, height: 20)
                }
            }
        }
        .font(.title)
        Button("Add to Red") { redScore += 1 }
        Button("Add to Blue") { blueScore += 1 }
    }
}

// 第2个方法:是将视图放入网格中,而不将它们包装在 GridRow 中,这将导致它们自己占据整行。这对于 Divider 视图非常有用
// 第3个方法:是使用 gridCellColumns() 修饰符,使一个单元格跨越多个列
Grid {
    GridRow {
        Text("Food")
        Text("$200")
    }

    GridRow {
        Text("Rent")
        Text("$800")
    }

    Divider()

    GridRow {
        Text("$4600")
            .gridCellColumns(2)
            .multilineTextAlignment(.trailing)
    }

}
.font(.title)