How to drag across Views in a LazyVGrid with GeometryReader?

548 Views Asked by At

I want to drag across rectangles in a grid and change their color. This code is almost working, but this is not always the right rectangle that reacts: it behaves rather randomly. Any hints?

import SwiftUI


struct ContentView: View {
    let data = (0...3)
    
    @State private var colors: [Color] = Array(repeating: Color.gray, count: 4)
    @State private var rect = [CGRect]()
    
    var columns: [GridItem] =
        Array(repeating: .init(.fixed(70), spacing: 1), count: 2)
    
    var body: some View {
        LazyVGrid(columns: columns, spacing: 1) {
            ForEach(data, id: \.self) { item in
                Rectangle()
                    .fill(colors[item])
                    .frame(width: 70, height: 70)
                    .overlay(
                        GeometryReader{ geo in
                            Color.clear
                                .onAppear {
                                    rect.insert(geo.frame(in: .global), at: rect.endIndex)
                                }
                        }
                    )
            }
        }
        .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
                .onChanged({ (value) in
                    if let match = rect.firstIndex(where: { $0.contains(value.location) }) {
                        colors[match] = Color.red
                    }
                })
        )
    }
}
1

There are 1 best solutions below

0
On BEST ANSWER

If I correctly understood your goal, here is fixed variant (with small modifications to avoid repeated hardcoding).

Tested with Xcode 12.1 / iOS 14.1

demo

struct ContentView: View {
    let data = (0...3)
    
    @State private var colors: [Color]
    @State private var rect: [CGRect]
    
    init() {
        _colors = State(initialValue: Array(repeating: .gray, count: data.count))
        _rect = State(initialValue: Array(repeating: .zero, count: data.count))
    }
    
    var columns: [GridItem] =
        Array(repeating: .init(.fixed(70), spacing: 1), count: 2)
    
    var body: some View {
        LazyVGrid(columns: columns, spacing: 1) {
            ForEach(data, id: \.self) { item in
                Rectangle()
                    .fill(colors[item])
                    .frame(width: 70, height: 70)
                    .overlay(
                        GeometryReader{ geo in
                            Color.clear
                                .onAppear {
                                    rect[item] = geo.frame(in: .global)
                                }
                        }
                    )
            }
        }
        .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
                        .onChanged({ (value) in
                            if let match = rect.firstIndex(where: { $0.contains(value.location) }) {
                                colors[match] = Color.red
                            }
                        })
        )
    }
}