如何在 SwiftUI LazyVGrid 中创建自定义布局以跨越多行?

我想让单个单元格(centerCellView)跨越多个列和行,占据 5x4 网格中 6、7、8、11、12 和 13 号位置(2 行 x 3 列)。以下是当前布局的截图:

private func gridSection() -> some View {
      let spacing: CGFloat = 10
      let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: spacing), count: gridColumns)
      
      return GeometryReader { geometry in
         let totalSpacing = spacing * CGFloat(gridColumns - 1)
         let cellSize = (geometry.size.width - totalSpacing - 20) / CGFloat(gridColumns)
         
         Color("FFEA6D")
            .onAppear {
               gridCellSize = cellSize
            }
         
         LazyVGrid(columns: columns, spacing: spacing) {
            ForEach(0..<(gridRows * gridColumns), id: \.self) { index in
               if index == 6 {
                  centerCellView(size: cellSize)
                     .frame(width: cellSize * 3 + spacing * 2, height: cellSize * 2 + spacing)
                     .gridCellColumns(3)
                     .gridCellAnchor(.topLeading)
               } else if [7, 8, 11, 12, 13].contains(index) {
                  EmptyView()
                     .frame(width: cellSize, height: cellSize)
               } else {
                  normalCellView(size: cellSize)
               }
            }
         }
         .padding(.horizontal, 10)
         .padding(.vertical, 20)
      }
      .frame(height: gridCellSize * CGFloat(gridRows) + spacing * CGFloat(gridRows - 1) + 40)
   }
   
   private func centerCellView(size: CGFloat) -> some View {
      RoundedRectangle(cornerRadius: 20)
         .fill(Color("FFEA6D"))
         .stroke(isFlipped ? Color("12B754") : Color("FF403D"), lineWidth: 2)
         .overlay(
            Text(isFlipped ? "180.00¢" : "0.00¢")
               .font(.system(size: 20, weight: .bold))
               .foregroundColor(isFlipped ? Color("12B754") : Color("FF403D"))
         )
   }
   
   private func normalCellView(size: CGFloat) -> some View {
      Circle()
         .stroke(isFlipped ? Color("12B754") : Color("FF403D"), lineWidth: 2)
         .frame(width: size, height: size)
         .overlay(
            Image("generic_avatar")
               .resizable()
               .scaledToFill()
               .foregroundColor(isFlipped ? Color("44BD86") : Color("FF8673"))
               .frame(width: size * 0.9, height: size * 0.9)
               .clipShape(Circle())
         )
         .rotation3DEffect(
            .degrees(isFlipped ? 180 : 0),
            axis: (x: 0.0, y: 1.0, z: 0.0),
            perspective: 0.3
         )
         .animation(.spring(response: 0.5, dampingFraction: 0.85), value: isFlipped)
   }
阅读 599
1 个回答

在 SwiftUI 的 LazyVGrid 中实现单元格跨行跨列需要巧妙的布局策略,因为 LazyVGrid 本身不支持直接的跨行功能(仅支持跨列)。以下是基于最佳实践的解决方案:

private func gridSection() -> some View {
    let spacing: CGFloat = 10
    let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: spacing), count: gridColumns)
    
    return GeometryReader { geometry in
        let totalSpacing = spacing * CGFloat(gridColumns - 1)
        let cellSize = (geometry.size.width - totalSpacing - 20) / CGFloat(gridColumns)
        
        ZStack(alignment: .topLeading) {
            // 基础网格布局
            LazyVGrid(columns: columns, spacing: spacing) {
                ForEach(0..<(gridRows * gridColumns), id: \.self) { index in
                    if ![6,7,8,11,12,13].contains(index) {
                        normalCellView(size: cellSize)
                    } else {
                        Color.clear // 占位但透明
                            .frame(width: cellSize, height: cellSize)
                    }
                }
            }
            .padding(.horizontal, 10)
            .padding(.vertical, 20)
            
            // 叠加跨行跨列视图(关键技巧)
            let startX = 10 + (cellSize + spacing) * CGFloat(1) // 第2列起始位置
            let startY = 20 + (cellSize + spacing) * CGFloat(1) // 第2行起始位置
            let width = cellSize * 3 + spacing * 2
            let height = cellSize * 2 + spacing
            
            centerCellView(size: cellSize)
                .frame(width: width, height: height)
                .offset(x: startX, y: startY) // 精确定位
        }
    }
    .frame(height: gridCellSize * CGFloat(gridRows) + spacing * CGFloat(gridRows - 1) + 40)
}