SwiftUI Drag-and-Drop
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 |
Purpose of this library.
This library supports drag-and-drop in SwiftUI code. This is a replacement for the native onDrag
/ onDrop
and their limitations.
Which Use Cases Does This Library Fit?
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.
Getting Started.
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.
In-Depth Examples of Implementation.
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.