[SwiftUI 开发] LazyVGrid & LazyHGrid 网格(UICollectionView)

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
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值