DNB5brims
DNB5brims

Reputation: 30618

Is this possible to return which griditem in LazyVGrid using position in SwiftUI?

I got a LazyVGrid, and I would like to know is this possible to return which griditem by given a gesture.location. Thanks.

Upvotes: 1

Views: 182

Answers (1)

Ashley Mills
Ashley Mills

Reputation: 53181

To store the location of each item in a LazyVGrid, do this using PreferenceKey:

First define an object to store the frame for each view in the grid:

struct ModelFrame: Hashable {
    let id: String
    let frame: CGRect
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

then create a PreferenceKey as follows:

struct ModelFrameKey: PreferenceKey {
    static var defaultValue: Set<ModelFrame> = []
    
    static func reduce(value: inout Set<ModelFrame>, nextValue: () -> Set<ModelFrame>) {
        value = value.union(nextValue())
    }
}

Then for each view in the grid, add a clear background wrapped in a GeometryReader with .preference modifier to store the frame for the view.

struct Model: Identifiable {
    var id: String { name }
    let name: String
}

struct ContentView: View {
    
    let models = ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu"].map(Model.init)
    
    @State private var modelFrames: Set<ModelFrame> = []
    @State private var overModel: Model?
    
    var body: some View {
        VStack {
            Text("Over: \(overModel?.name ?? "nothing")")
                .font(.title)
            LazyVGrid(columns: [GridItem(), GridItem()]) {
                ForEach(models) { model in
                    Text(model.name)
                        .padding()
                        .background(overModel?.id == model.id ? .red : .yellow, in: RoundedRectangle(cornerRadius: 8))
                        .background {
                            GeometryReader { proxy in
                                Color.clear.preference(key: ModelFrameKey.self, value: [ModelFrame(id: model.id, frame: proxy.frame(in: .named("Grid")))])
                            }
                        }
                }
            }
            .coordinateSpace(name: "Grid")
            .onPreferenceChange(ModelFrameKey.self) { frames in
                self.modelFrames = frames
            }
            .gesture(
                DragGesture()
                    .onChanged { value in
                        overModel = model(containing: value.location)
                    }
            )
        }
    }
    
    func model(containing point: CGPoint) -> Model? {
        guard let id = modelFrames.first(where: { $0.frame.contains(point) })?.id else {
            return nil
        }
        return models.first(where: { $0.id == id })
    }

Now you have your frames, you can use CGRect.contains(CGPoint) in your drag gesture to see which view contains the current location.

enter image description here

Upvotes: 2

Related Questions