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

SwiftUI拖拽手势问题:网格遮罩未从手指落点开始移除

解决SwiftUI拖拽移除遮罩时从左上角开始的问题

你的问题出在每个遮罩单元格单独绑定了DragGesture,此时手势回调里的value.location是相对于当前单元格的局部坐标,不是整个网格的全局坐标。比如你拖拽到任意单元格,它的局部x、y都会是0到50之间,除以50后得到0,所以总是修改左上角的topGridRevealed[0][0]。

修复方案

把DragGesture绑定到整个遮罩网格的外层容器上,这样获取的坐标是相对于整个网格的全局坐标,计算出来的行列位置才准确。同时可以优化逻辑,避免重复设置同一个单元格为true。

修改后的完整代码:

import SwiftUI

struct ContentView: View {
    @State var bottomGridColors: [[Color]] = Array(repeating: [.red, .orange, .yellow, .green, .blue, .purple, .pink, .gray], count: 8)
    @State private var topGridRevealed = [[Bool]](repeating: Array(repeating: false, count: 8), count: 8)
    // 记录已经处理过的单元格,避免重复操作
    @State private var processedCells = Set<CGPoint>()
    
    var body: some View {
        ZStack {
            // 底层颜色网格
            VStack(spacing: 0) {
                ForEach(0..<8, id: \.self) { row in
                    HStack(spacing: 0) {
                        ForEach(0..<8, id: \.self) { col in
                            Rectangle()
                                .fill(bottomGridColors[row][col])
                                .frame(width: 50, height: 50)
                        }
                    }
                }
            }
            
            // 顶层遮罩网格,把手势绑定到外层VStack
            VStack(spacing: 0) {
                ForEach(0..<8, id: \.self) { row in
                    HStack(spacing: 0) {
                        ForEach(0..<8, id: \.self) { col in
                            Rectangle()
                                .fill(topGridRevealed[row][col] ? .clear : .black)
                                .frame(width: 50, height: 50)
                        }
                    }
                }
            }
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { value in
                        let cellSize: CGFloat = 50
                        // 计算当前触摸点对应的行列
                        let x = Int(value.location.x / cellSize)
                        let y = Int(value.location.y / cellSize)
                        
                        // 检查行列是否在有效范围内,且未被处理过
                        guard x >= 0, x < 8, y >= 0, y < 8, !processedCells.contains(CGPoint(x: x, y: y)) else { return }
                        
                        topGridRevealed[y][x] = true
                        processedCells.insert(CGPoint(x: x, y: y))
                    }
                    .onEnded { _ in
                        // 拖拽结束后清空已处理记录,下次拖拽可以重新操作
                        processedCells.removeAll()
                    }
            )
        }
    }
}

struct ContentViewPreview: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

关键修改说明

  1. 手势移到外层:将DragGesture绑定到整个遮罩网格的VStack上,确保value.location是相对于整个网格的坐标,计算出来的x、y是正确的行列索引。
  2. 添加已处理单元格记录:用Set<CGPoint>记录已经移除遮罩的单元格,避免拖拽过程中重复设置同一个单元格,提升性能。
  3. 优化初始化代码:用Array(repeating: ...)简化颜色网格的初始化,代码更简洁。

如果需要适配不同屏幕尺寸,建议用GeometryReader获取网格的实际宽高,动态计算单元格大小,而不是固定50。比如:

GeometryReader { geo in
    let cellSize = min(geo.size.width, geo.size.height) / 8
    // 后续用cellSize代替固定的50
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 00:55:19