Skip to content

Instantly share code, notes, and snippets.

@wonderbit
Last active March 20, 2024 12:27
Show Gist options
  • Save wonderbit/b77c442d4485adfbd4523ab18e5ab263 to your computer and use it in GitHub Desktop.
Save wonderbit/b77c442d4485adfbd4523ab18e5ab263 to your computer and use it in GitHub Desktop.
A working implementation of drag and drop between two LazyVGrid elements in SwiftUI
//
// DragAndDropView.swift
// DragAndDropView_Example
//
// Created by Wessley Roche on 11/5/23.
//
import SwiftUI
import UniformTypeIdentifiers
struct DragAndDropView: View {
@State private var leftItems = Array(1...10)
@State private var rightItems = Array(11...20)
@State var draggedItem: Int?
let columns = [
GridItem(.adaptive(minimum: 80))
]
var body: some View {
HStack {
GeometryReader { geometry in
LazyVGrid(columns: columns, spacing: 20) {
ForEach(leftItems, id: \.self) { item in
Text("\(item)")
.frame(maxWidth: .infinity, minHeight: 50)
.background(Color.green)
.cornerRadius(8)
.onDrag {
draggedItem = item
return NSItemProvider(object: String(item) as NSString)
}
.opacity(draggedItem == item ? 0 : 1)
}
}
.frame(minHeight: 80)
.background(Color.gray)
.onDrop(of: [.text], delegate: MyDropDelegato(draggedItem: $draggedItem, items: $leftItems, geometry: geometry))
}
GeometryReader { geometry in
LazyVGrid(columns: columns, spacing: 20) {
ForEach(rightItems, id: \.self) { item in
Text("\(item)")
.frame(maxWidth: .infinity, minHeight: 50)
.background(Color.green)
.cornerRadius(8)
.onDrag {
draggedItem = item
return NSItemProvider(object: String(item) as NSString)
}
.opacity(draggedItem == item ? 0 : 1)
}
}
.frame(minHeight: 80)
.background(Color.gray)
.onDrop(of: [.text], delegate: MyDropDelegato(draggedItem: $draggedItem, items: $rightItems, geometry: geometry))
}
}
.padding()
}
}
struct MyDropDelegato: DropDelegate {
/// The item currently being dragged
@Binding var draggedItem: Int?
/// The items in the grid
@Binding var items: [Int]
/// The geometry of the grid
var geometry: GeometryProxy
func performDrop(info: DropInfo) -> Bool {
withAnimation {
/// Reset dragged item
draggedItem = nil
}
return true
}
func dropUpdated(info: DropInfo) -> DropProposal? {
/// Get the index from the drop info location
let destination = getIndexForItem(itemPosition: info.location)
withAnimation {
if let item = draggedItem, let origin = items.firstIndex(of: item) {
let adjustedDestination = destination < origin ? destination : min(destination + 1, items.count)
items.move(fromOffsets: IndexSet(integer: origin), toOffset: adjustedDestination)
}
}
return DropProposal(operation: .move)
}
func dropEntered(info: DropInfo) {
guard let item = draggedItem else { return }
if items.firstIndex(of: item) == nil {
items.append(item)
}
}
func dropExited(info: DropInfo) {
guard let item = draggedItem else { return }
if let index = items.firstIndex(of: item) {
items.remove(at: index)
}
}
func getIndexForItem(itemPosition: CGPoint) -> Int {
let itemWidth: CGFloat = 80
let itemHeight: CGFloat = 50
let spacing: CGFloat = 20
let numberOfColumns = Int(geometry.size.width / itemWidth)
let column = Int(itemPosition.x / itemWidth)
let row = Int(itemPosition.y / (itemHeight + spacing))
let index = row * numberOfColumns + column
return index
}
}
struct DragAndDropView_Previews: PreviewProvider {
static var previews: some View {
DragAndDropView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment