Search code examples
swiftuidraggesture

My drag tracker in a rectangle is erratic about whether it's listening


I have a 10x10 grid surrounded by a rectangle where I want to be able to select a group of cells. My intent is to track the pointer in the rectangle and calculate the coordinate for each cell it passes over as I drag through the array. I'm using Swift 5.9 in Xcode 15.1 on a MacBook Pro and the iPhone 15 Pro simulator. Right now, I'm just trying to print the mouse coordinates as the pointer passes through the rectangle and the calculated cell coordinates. On my Mac, when I click and start to drag, it is erratic about responding to the dragGesture. Once it starts tracking, it tracks reliably until I let up from the trackpad. But then it's erratic about starting again. The probability is higher that it will track if I start near the border of the rectangle.
Here I have to a simple view showing a 300x300 rectangle that I want drag through and see the pointer coords as I drag. When the dragGesture responds, I print the drag coordinates and the row and column of the cell that would have been selected. I want it would track every time I pressed on the trackpad inside the rectangle and stop when I lift up.

struct ContentView: View {
    var cellSize: Double = 30.0
    
    var body: some View {
        ZStack(){
            Rectangle()
                .stroke(.red, lineWidth: 6)
                .fill(.clear)
                .frame(width: 10 * cellSize, height: 10 * cellSize)
                .gesture(DragGesture(minimumDistance: 2,
                                     coordinateSpace: .local)
                    .onChanged() { drag in
                        print("Drag: raw: \(drag.location); Cell: \(Int(drag.location.y/cellSize)), \(Int(drag.location.x/cellSize))")
                    }
                    .onEnded() { drag in
                        print("DragEnd: \(drag.location)")
                    }
                )
        }
    }
}

Solution

  • Try this approach using minimumDistance: 0 and .fill(.white.opacity(0.001)) instead of .fill(.clear). As you drag the mouse or finger inside the red bordered rectangle, the Cell info is displayed and printed.

    Note, using .fill(.clear) does not capture the drag events for the Rectangle, but it does, if you use a color, such as .fill(.white.opacity(0.0001)), the equivalent of clear.

    struct ContentView: View {
        let cellSize: Double = 30.0
        @State var info = "no info" // <-- for testing
    
        var body: some View {
            VStack {
                Text(info)  // <-- for testing
                Rectangle()
                    .stroke(.red, lineWidth: 6)
                    .fill(.white.opacity(0.0001))  // <-- here
                    .frame(width: 10 * cellSize, height: 10 * cellSize)
                    .gesture(DragGesture(minimumDistance: 0) // <-- here
                        .onChanged() { drag in
                            info = "Cell: \(Int(drag.location.y/cellSize)), \(Int(drag.location.x/cellSize))"
                            print("Drag: raw: \(drag.location); Cell: \(Int(drag.location.y/cellSize)), \(Int(drag.location.x/cellSize))")
                        }
                        .onEnded() { drag in
                            print("DragEnd: \(drag.location)")
                        }
                    )
            }
        }
    }