You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在LazyVGrid中正确使用.zIndex()解决瓦片遮挡问题

问题:LazyVGrid中展开的瓦片被其他瓦片遮挡

我在2×2的LazyVGrid中放置了4个自定义瓦片视图,点击时瓦片会展开或收缩。我使用.zIndex()让展开的瓦片置于Z轴顶层,以避免被其余3个瓦片遮挡,但部分瓦片仍会被其他瓦片遮挡。

问题场景说明

  • 所有瓦片未展开:4个灰色小瓦片按2×2网格排列
  • 左上角瓦片展开:蓝色大瓦片被右上角和左下角的瓦片部分遮挡
  • 右上角瓦片展开:蓝色大瓦片被左上角和右下角的瓦片部分遮挡
  • 左下角瓦片展开:蓝色大瓦片被左上角和右下角的瓦片部分遮挡
  • 右下角瓦片展开:蓝色大瓦片被右上角和左下角的瓦片部分遮挡

原代码

struct SetupView: View {
    @State private var expandedTile: Int? = nil
    let columns: [GridItem] = [GridItem(.flexible()), GridItem(.flexible())]

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(0..<4, id: \.self) { index in
                        OperationTileView(
                            tileSize: geometry.size.width / 2 ,
                            tileIndex: index,
                            alignment: alignment(for: index),
                            expandedTile: $expandedTile
                        )
                        .zIndex(expandedTile == index ? 1 : 0)
                    }
                }
            }
        }
    }

    // 确定对应索引瓦片的对齐方式
    private func alignment(for index: Int) -> Alignment {
        switch index {
        case 0: return .topLeading
        case 1: return .topTrailing
        case 2: return .bottomLeading
        case 3: return .bottomTrailing
        default: fatalError("无效索引")
        }
    }
}


struct OperationTileView: View {

    let tileSize: CGFloat
    let tileIndex: Int
    let alignment: Alignment
    @Binding var expandedTile: Int?
    
    private var isExpanded: Bool {
        expandedTile == tileIndex
    }
    
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(isExpanded ? Color.blue: Color.gray)
                .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5)
        }
        .frame(width: isExpanded ? tileSize * 2 : tileSize, height: isExpanded ? tileSize * 2 : tileSize, alignment: alignment)
        .offset(isExpanded ? offset(for: alignment) : .zero)
        .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isExpanded)
        .onTapGesture {
            withAnimation {
                expandedTile = isExpanded ? nil : tileIndex
            }
        }
    }
    
    private func offset(for alignment: Alignment) -> CGSize {
        switch alignment {
            case .topLeading: return CGSize(width: tileSize/2, height: tileSize/2)
            case .topTrailing: return CGSize(width: -(tileSize/2), height: tileSize/2)
            case .bottomLeading: return CGSize(width: tileSize/2, height: -(tileSize/2))
            case .bottomTrailing: return CGSize(width: -(tileSize/2), height: -(tileSize/2))
            default: return .zero
        }
    }
}
解决方法

问题根源在于LazyVGrid的布局容器特性:子视图的.zIndex()仅在同一单元格或布局组内生效,跨单元格的视图层级无法被直接覆盖。要彻底解决遮挡,需将展开的瓦片从网格布局流中抽离,放到独立的ZStack层级。

修改后的代码

struct SetupView: View {
    @State private var expandedTile: Int? = nil
    let columns: [GridItem] = [GridItem(.flexible()), GridItem(.flexible())]

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // 底层:仅显示未展开的瓦片
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(0..<4, id: \.self) { index in
                        if expandedTile != index {
                            OperationTileView(
                                tileSize: geometry.size.width / 2 ,
                                tileIndex: index,
                                alignment: alignment(for: index),
                                expandedTile: $expandedTile
                            )
                        }
                    }
                }
                
                // 顶层:显示展开的瓦片(如果有)
                if let expandedIndex = expandedTile {
                    OperationTileView(
                        tileSize: geometry.size.width / 2 ,
                        tileIndex: expandedIndex,
                        alignment: alignment(for: expandedIndex),
                        expandedTile: $expandedTile
                    )
                    .zIndex(1) // 确保处于顶层
                }
            }
        }
    }

    private func alignment(for index: Int) -> Alignment {
        switch index {
        case 0: return .topLeading
        case 1: return .topTrailing
        case 2: return .bottomLeading
        case 3: return .bottomTrailing
        default: fatalError("无效索引")
        }
    }
}


struct OperationTileView: View {

    let tileSize: CGFloat
    let tileIndex: Int
    let alignment: Alignment
    @Binding var expandedTile: Int?
    
    private var isExpanded: Bool {
        expandedTile == tileIndex
    }
    
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(isExpanded ? Color.blue: Color.gray)
                .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5)
        }
        .frame(width: isExpanded ? tileSize * 2 : tileSize, height: isExpanded ? tileSize * 2 : tileSize, alignment: alignment)
        .offset(isExpanded ? offset(for: alignment) : .zero)
        .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isExpanded)
        .onTapGesture {
            withAnimation {
                expandedTile = isExpanded ? nil : tileIndex
            }
        }
    }
    
    private func offset(for alignment: Alignment) -> CGSize {
        switch alignment {
            case .topLeading: return CGSize(width: tileSize/2, height: tileSize/2)
            case .topTrailing: return CGSize(width: -(tileSize/2), height: tileSize/2)
            case .bottomLeading: return CGSize(width: tileSize/2, height: -(tileSize/2))
            case .bottomTrailing: return CGSize(width: -(tileSize/2), height: -(tileSize/2))
            default: return .zero
        }
    }
}

核心改动说明

  1. 过滤原LazyVGrid中的瓦片,仅渲染未展开的项
  2. 在ZStack顶层单独渲染当前展开的瓦片,确保它处于所有网格瓦片之上
  3. 保留原有的动画、尺寸计算和点击逻辑,保证交互体验一致

内容的提问来源于stack exchange,提问作者Steve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:24:57