Grid布局
LazyVGrid & LazyHGrid布局都需要[GridItem]数组来控制约束的
GridItem有3种约束类型
public enum Size {
// 固定Item大小,根据数组数量,显示每行个数
case fixed(CGFloat)
// flexible: 根据数组数量,显示每行个数。 可以把固定size和可变size放到一行。缺点是如果放不下,就会超出屏幕
case flexible(minimum: CGFloat = 10, maximum: CGFloat = .infinity)
// 根据设置的参数,一行能放下多少个就放多少个
case adaptive(minimum: CGFloat, maximum: CGFloat = .infinity)
}
1. fixed
固定Item大小,根据数组数量,显示每行个数
struct ContentView: View {
// fixed: 固定Item大小,根据数组数量,显示每行个数
var columns: [GridItem] = [GridItem(.fixed(80)),GridItem(.fixed(80)),GridItem(.fixed(80))]
var body: some View {
LazyVGrid(columns: columns, spacing: 10) {
ForEach((0...11), id: \.self) {index in
Text("\(index)")
.frame( minWidth: 50, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
}
}.font(.largeTitle)
.padding()
}
}
2. adaptive
根据设置的参数,一行能放下多少个就放多少个
struct ContentView: View {
// adaptive: 根据设置的参数,一行能放下多少个就放多少个
var columns: [GridItem] = [GridItem(.adaptive(minimum: 50, maximum: 80))]
var body: some View {
LazyVGrid(columns: columns, spacing: 10) {
ForEach((0...11), id: \.self) {index in
Text("\(index)")
.frame( minWidth: 50, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
}
}.font(.largeTitle)
.padding()
}
}
3. flexible
根据数组数量,显示每行个数。 可以把固定size和可变size放到一行。缺点是如果放不下,就会超出屏幕
struct ContentView: View {
// flexible: 根据数组数量,显示每行个数。 可以把固定size和可变size放到一行。缺点是如果放不下,就会超出屏幕
var columns: [GridItem] = [GridItem(.flexible(minimum: 150)), GridItem(), GridItem(), GridItem()]
var body: some View {
LazyVGrid(columns: columns, spacing: 10) {
ForEach((0...11), id: \.self) {index in
Text("\(index)")
.frame( minWidth: 50, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
}
}.font(.largeTitle)
.padding()
}
}
LazyVGrid滑动
如果想让LazyVGrid能滑动需要外层套上 ScrollView() { }
ScrollView() {
LazyVGrid(columns: columns, spacing: 10) {
ForEach((0...11), id: \.self) {index in
Text("\(index)")
.frame( minWidth: 50, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
}
}.font(.largeTitle)
.padding()
}
LazyVGrid的特殊布局
在[GridItem]数组做约束的情况下,每行的样式都是相同的
做成这样有不同约束的行,我用的是修改6的minWidth,在原本7的位置再添加一个Spacer()来占位。这样看上去效果达到了
struct ContentView: View {
var columns: [GridItem] = [GridItem(.flexible(minimum: 0, maximum: .infinity)), GridItem(), GridItem()]
var body: some View {
LazyVGrid(columns: columns, alignment: .leading, spacing: 20) {
ForEach((0...10), id: \.self) {index in
if index == 6 {
Text("\(index)").frame(minWidth: 260, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
Spacer()
} else {
Text("\(index)")
.frame( minWidth: 50, maxWidth: .infinity)
.background(.teal)
.cornerRadius(5)
.shadow(radius: 5)
}
}
}.font(.largeTitle)
}
}
LazyVGrid实际应用
蜂窝布局
1. 使用Shape,绘制六边形
2. 单行和双行之间我们要错落一下,使用.offset来改变itme.x的位置
3. Slider用来更改LazyVGrid的spacing
点击item有动画提示
实际应用中我们可能要选择某个item, 相应的代码逻辑我们可以放到.onTapGesture中去
Demo参考地址: Impossible Grids with SwiftUI - The SwiftUI Lab
struct ContentView: View {
let cols: Int = 6
@State var spacing: CGFloat = 10
@State var animationAmount = [Int:Bool]()
let imgsize = CGSize(width: 150, height: 150)
var hexagonWidth: CGFloat { (imgsize.width / 2) * cos(.pi / 6) * 2 }
var body: some View {
let gridItems = Array(repeating: GridItem(.fixed(hexagonWidth), spacing: spacing), count: cols)
VStack {
ScrollView(.vertical) {
LazyVGrid(columns: gridItems, spacing: spacing) {
ForEach(0..<150) { idx in
VStack(spacing: 0) {
Image("image-\(idx % 15)")
.resizable()
.frame(width: imgsize.width, height: imgsize.height)
.clipShape(PolygonShape(sides: 6).rotation(Angle.degrees(90)))
.rotation3DEffect(Angle(degrees: animationAmount[idx] ?? true ? 360 : 0), axis: (x: 0.0, y: 1.0, z: 0.0))
.offset(x: (idx / cols) % 2 == 0 ? 0 : hexagonWidth / 2 + (spacing/2))
.onTapGesture {
withAnimation(.interpolatingSpring(stiffness: 30, damping: 3)){
self.animationAmount[idx] = !(self.animationAmount[idx] ?? true)
}
}
}
.frame(width: hexagonWidth, height: imgsize.height * 0.75)
}
}
.frame(width: (hexagonWidth + spacing) * CGFloat(cols-1))
}
Slider(value: $spacing, in: 0...10)
}
}
func isEvenRow(_ idx: Int) -> Bool { (idx / cols) % 2 == 0 }
}
struct PolygonShape: Shape {
var sides: Int
func path(in rect: CGRect) -> Path {
let h = Double(min(rect.size.width, rect.size.height)) / 2.0
let c = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
var path = Path()
for i in 0..<sides {
let angle = (Double(i) * (360.0 / Double(sides))) * Double.pi / 180
let pt = CGPoint(x: c.x + CGFloat(cos(angle) * h), y: c.y + CGFloat(sin(angle) * h))
if i == 0 {
path.move(to: pt) // move to first vertex
} else {
path.addLine(to: pt) // draw line to next vertex
}
}
path.closeSubpath()
return path
}
}