SwiftUI | customize dragitem appearance within LazyGrid using onDrag

Viewed 374

I've been wondering if there is any way to customize the preview image of the item that's being dragged when using onDrag within a LazyGrid?

As you might see, the preview image stays static, while all other items change their heights. I would like it to have the smaller height as well.

Like so:

Since a rendering of that view is generated and used as the preview image at the very beginning of the dragging process, before the height of the items are set, I have no idea how one would realize that.

One idea would be to have a LongPressGesture before the onDrag gesture. That way we could set the height before onDrag starts, which would lead to a snapshot of the new height. The problem I am facing is to combine these gestures so that they function like one, without the need to release between them.

Maybe there is another way or I have misunderstood something?

Here is the full demo code:

import SwiftUI
import UniformTypeIdentifiers

struct GridData: Identifiable, Equatable {
    let id: Int
}

//MARK: - Model

class Model: ObservableObject {
    @Published var data: [GridData]

    let columns = [
        GridItem(.fixed(160)),
        GridItem(.fixed(160))
    ]

    init() {
        data = Array(repeating: GridData(id: 0), count: 100)
        for i in 0..<data.count {
            data[i] = GridData(id: i)
        }
    }
}

//MARK: - Grid

struct ContentView: View {
    @StateObject private var model = Model()

    @State private var dragging: GridData?
    @State private var onDrag = false

    var body: some View {
        ScrollView {
           LazyVGrid(columns: model.columns, spacing: 32) {
                ForEach(model.data) { d in
                    GridItemView(d: d, onDrag: $onDrag)
                        .overlay(dragging?.id == d.id ? Color.white.opacity(0.8) : Color.clear)
                        .onDrag {
                            self.onDrag = true
                            self.dragging = d
                            
                            return NSItemProvider(object: String(d.id) as NSString)
                        }
                        .onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging, onDrag: $onDrag))
                }
           }
           .animation(.easeInOut(duration: 0.3), value: model.data)
        }
    }
}

//MARK: - Drag and Drop

struct DragRelocateDelegate: DropDelegate {
    let item: GridData
    @Binding var listData: [GridData]
    @Binding var current: GridData?
    @Binding var onDrag: Bool

    func dropEntered(info: DropInfo) {
        if item != current {
            let from = listData.firstIndex(of: current!)!
            let to = listData.firstIndex(of: item)!
            if listData[to].id != current!.id {
                listData.move(fromOffsets: IndexSet(integer: from),
                    toOffset: to > from ? to + 1 : to)
            }
        }
    }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        return DropProposal(operation: .move)
    }

    func performDrop(info: DropInfo) -> Bool {
        self.onDrag = false
        self.current = nil
        return true
    }
}

//MARK: - GridItem

struct GridItemView: View {
    var d: GridData
    @Binding var onDrag: Bool

    var body: some View {
        VStack {
            Text(String(d.id))
                .font(.headline)
                .foregroundColor(.white)
        }
        .frame(width: 160, height: onDrag ? 140 : 240)
        .background(Color.green)
        .animation(.easeInOut(duration: 0.6))
    }
}

Thanks!

0 Answers
Related