Drag-and-drop is an intuitive gesture and can improve the UX of an app.
Chess | Emoji Art | Card Game | To Do List |
---|---|---|---|
Documentation | Documentation | Documentation | Documentation |
This library supports drag-and-drop in SwiftUI code. This is a replacement for the native onDrag
/ onDrop
and their limitations.
This library is a good fit if your use case falls into one or more of the following:
- If you need or want to support drag-and-drop below iOS 16 (namely, iOS 13 or higher).
- If you are drag-and-dropping non-NSObjects.
- If you are not drag-and-dropping between apps (e.g., dragging URL from Safari to your app on an iPad).
- If you are not (for whatever reason) enchanted with Apple's implementation.
- If you like the way this library organizes drop logic on the draggable object rather than the drop-receiving object.
The following steps will get your project compiling with a basic implementation of the library.
- Choose a type to conform to
DropReceiver
and conform the struct to it:
protocol DropReceiver {
var dropArea: CGRect? { get set }
}
For example:
struct MyDropReceiver: DropReceiver {
var dropArea: CGRect? = nil
}
- Conform your ViewModel to
DropReceivableObservableObject
& Add a variable in the ViewModel referencing theDropReceiver
struct.
protocol DropReceivableObservableObject: ObservableObject {
associatedtype DropReceivable: DropReceiver
func setDropArea(_ dropArea: CGRect, on dropReceiver: DropReceivable)
}
For example:
class DragAndDropViewModel: DropReceivableObservableObject {
typealias DropReceivable = MyDropReceiver
var dropReceiver = MyDropReceiver()
func setDropArea(_ dropArea: CGRect, on dropReceiver: DropReceivable) {
dropReceiver.updateDropArea(with: dropArea)
}
}
- Add the ViewModifier
.dropArea(for:model:)
to an element in your View which represents theDropReceiver
struct. Thefor:
should be an element of the type of theDropReceiver
struct and themodel:
should be a reference to theDropReceivableObservableObject
ViewModel.
For example:
struct MyDragAndDropView: View {
@State var model = DragAndDropViewModel()
var body: some View {
VStack {
Rectangle()
.dropReceiver(for: model.dropReceiver, model: model)
}
}
}
- Add the ViewModifier
.dragable()
to any other element in the View. The code can now run and the View is draggable. It will show a blue shadow while dragging.
For example:
var body: some View {
VStack {
Rectangle()
.frame(width: 150, height: 150)
.dropReceiver(for: model.dropReceiver, model: model)
Spacer()
Circle()
.frame(width: 50, height: 50)
.dragable()
}
.padding()
}
- To see the dragged object and drop receiver interact, add these two function definitions to the View and assign them to
.dragable(onDragged:onDropped:)
, respectively.
func onDragged(position: CGPoint) -> DragState {
if model.dropReceiver.getDropArea()!.contains(position) {
return .accepted
} else {
return .rejected
}
}
func onDropped(position: CGPoint) -> Bool {
model.dropReceiver.getDropArea()!.contains(position)
}
This code will allow a user to drag the object marked dragable
and the shadow will now be green if the drop receiver is below the drag gesture or red if it is not.
These examples are intended to showcase various implementations of drag-and-drop and are not intended to be full apps. Since drag-and-drop is a means of signaling user intent, these examples show various ways to capture that intent.
Emoji Art.
View the code. | Read the Documentation.
This example shows a single drop receiver (the canvas) and multiple drag-and-drop objects (the emoji on the palette & the emoji on the canvas).
To Do App.
View the code. | Read the Documentation.
A todo list where each list object can be dragged on top of a "Complete" box or a "Trash" box. The "Add New" button is draggable on top of the list to add a new object.
Chess Board.
View the code. | Read the Documentation.
This example shows how to implement drag-and-drop chess pieces on the chess board. The only movement rules are basic directional rules. The movement rules do not enforce check, checkmate, turn order, or board-wrapping (that is, a bishop on a3 can move to h3 as described here).
Card Game.
View the code. | Read the Documentation.
In this game, players can play a card in one of three playable areas: here, there, or yonder. Each card can be played in 1 or more of these areas.
Working with Non-Rectangular Drop Areas.
This tutorial covers working with non-rectangular drop areas on a map or in a non-rectangular grid.