swiftuidrag

Can I eliminate a delay that I see when starting a drag-based selection process?


In Xcode 17 on a Mac, I'm trying to select cells in a 10x10 grid. I've put together the following code to just convince myself that it works. And it does seem to work except that there is a delay before it starts to color the cells. It's not a killer, but enough that it generally misses the first cell or two before it starts selecting. Once it gets going, it picks the cells very smartly. Is this delay something I just have to live with? My apologies, I'm an old-time Lisp programmer trying to remember how to program... Thanks for your help.

import SwiftUI

let cellSize: CGFloat = 30

struct ContentView: View {
    @State var cellColors: [[Color]] = Array(repeating: Array(repeating: .white, count: 10), count: 10)
    
    var body: some View {
        VStack {
            ZStack() {
                Rectangle()             //Bounding box for grid
                    .fill(Color.clear)
                    .stroke(Color.red, lineWidth: 0)
                    .frame(width: 10 * cellSize, height: 10 * cellSize)
                    .coordinateSpace(name: "Grid")
                
                VStack(spacing: 0){
                    ForEach (0..<10, id: \.self){row in
                        GridRow(content: {
                            HStack(spacing: 0){
                                ForEach (0..<10, id: \.self){column in
                                    Rectangle()
                                        .fill(cellColors[row][column])
                                        .stroke(Color.yellow, lineWidth: 2)
                                        .frame(width: cellSize, height: cellSize)
                                        .onTapGesture {coord in
                                            cellColors[row][column] = .red
                                            print("Tap: \(row), \(column)")
                                        }
                                }
                            }
                        })
                    }
                }
                .padding()
                .frame(width: 10 * cellSize, height: 10 * cellSize)
                .gesture(DragGesture(minimumDistance: 0,
                                     coordinateSpace: .named("Grid"))
                    .onChanged() { drag in
                        let cellRow = Int(drag.location.y/cellSize)
                        let cellColumn = Int(drag.location.x/cellSize)
                        cellColors[cellRow][cellColumn] = .blue
                    }
                    .onEnded() { drag in
                        print("DragEnd: \(drag.location)")
                    }
                )
            }
        }
    }
}

I clicked on a cell and started dragging. I expected (hoped, actually) that it would color the cell blue and continue until I stopped. There was almost always a short delay before it started coloring the cells. If I held the cursor in a cell and dragged very slowly, it would usually start coloring with the proper cell. Does that delay come from a source that I have some control over? Or is it just the way things are? I tried to clean up the code as best I could (this has been a steep learning curve!).


Solution

  • The DragGesture is "fighting" with the onTapGesture you added to the rectangles. The delay happens because the drag gesture is "waiting" to see if the tap gesture triggers.

    Tap gestures only triggers when you touch down for less than X milliseconds. If you touch down for too long then the tap gesture will not trigger. The drag gesture is basically waiting for X milliseconds, after that it knows you are not "tapping", and only then does it call its onChange.

    One way to solve this would be to turn the drag gesture into a high priority gesture, so it doesn't wait for the tap gesture. But now the tap gesture won't ever trigger, so you would also need to make the minimumDistance of the drag gesture non-zero, so that tap gestures can trigger when the user is not moving their finger.

    .highPriorityGesture(DragGesture(minimumDistance: 1, coordinateSpace: .named("Grid"))
    

    I did not test this value of 1 on a real device. 1 works on the simulator where I tap with my mouse, not sure if it works great with actual tapping by a finger. Try to find a minimum distance that is more than <however much peoples' fingers usually move when they tap>.