Last active
March 20, 2024 12:27
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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