Drag & Drop in SwiftUI

DevTechie Inc
Jun 24, 2022


Photo by Levi XU on Unsplash

iOS 15 introduced onDrag(_:preview:), which works with onDrop to provide a drag-and-drop functionality.

onDrag returns a view that activates this view as the source of drag and drop operation. It takes two parameters:

data: which is a closure that returns a single NSItemProvider type which represents the data which can be dragged from this view.

preview: is a view to use as the source for the dragging view, this is an optional parameter. Once drag operation begins, the preview is centered over the source view.

Drag operation starts with a long press gesture on the view.

Let’s try this with an example:

struct DragDropExample: View {
    @State private var text = "DevTechie"
    var body: some View {
        VStack {
            Text(text)
                .font(.largeTitle)
                .onDrag({
                    NSItemProvider(object: self.text as NSString)
                })
        }
        
    }
}
Here we are creating a Text view which has onDrag attached. Notice view’s behavior when its being dragged and when we let go.

At this point, view is being dragged but it doesn’t have any drop target.

Let’s add that drop target with the help of onDrop

OnDrop(of:delegate:)
onDrop defines the destination of a drag-and-drop operation using the behavior controlled by the defined delegate.

This returns a view that provides a drop destination for drag operation of the specified type.

onDrop takes three parameters:

supportedConentType: This is a uniform type identifier which describes the types of content this view can accept via drag-drop operation. If the drag and drop operation doesn’t contain any of the supported type then this drop destination doesn’t activate and isTargeted parameter doesn’t update.

delegate: is a type that conforms to DropDelegate protocol. This delegate gives total control over drop behavior.

Let’s implement DropDelegate in our code. Our struct will take two binding parameters, one for text and other for dropTargetText. We will simply swap these text values inside performDrop function as shown below:

struct DTDropTarget: DropDelegate {
    @Binding var text: String
    @Binding var dropTargetText: String
    
    func performDrop(info: DropInfo) -> Bool {
        text = ""
        dropTargetText = "DevTechie"
        return true
    }
}
We will update our DragDropExample view to add a rounded rectangle which will become drop target. We will also add another state property called dropTargetText which will be empty.

This rounded rect will also have onDrop modifier with supportedContent set as the text property and delegate set as the DTDropTarget:

struct DragDropExample: View {
    @State private var text = "DevTechie"
    @State private var dropTargetText = ""
    var body: some View {
        VStack {
            Text(text)
                .font(.largeTitle)
                .onDrag({
                    NSItemProvider(object: self.text as NSString)
                })
                
            RoundedRectangle(cornerRadius: 20)
                .fill(.orange)
                .frame(width: 200, height: 100)
                .overlay(Text(dropTargetText))
                .onDrop(of: [text], delegate: DTDropTarget(text: $text, dropTargetText: $dropTargetText))
        }
        
    }
}
Now, if you drag DevTechie over rounded rect, you will see it being added there:

We can reset this by resetting State properties as shown below:

struct DragDropExample: View {
    @State private var text = "DevTechie"
    @State private var dropTargetText = ""
    var body: some View {
        VStack {
            Text(text)
                .font(.largeTitle)
                .onDrag({
                    NSItemProvider(object: self.text as NSString)
                })
                
            RoundedRectangle(cornerRadius: 20)
                .fill(.orange)
                .frame(width: 200, height: 100)
                .overlay(Text(dropTargetText))
                .onDrop(of: [text], delegate: DTDropTarget(text: $text, dropTargetText: $dropTargetText))Button("Reset") {
                text = "DevTechie"
                dropTargetText = ""
            }
        }
        
    }
}
Drag text can be changed to a custom view as well. Let’s change ours to say “Drop me on orange”

struct DragDropExample: View {
    @State private var text = "DevTechie"
    @State private var dropTargetText = ""
    var body: some View {
        VStack {
            Text(text)
                .font(.largeTitle)
                .onDrag({
                    NSItemProvider(object: self.text as NSString)
                }) {
                    Text("Drop me on orange.")
                }
            
            RoundedRectangle(cornerRadius: 20)
                .fill(.orange)
                .frame(width: 200, height: 100)
                .overlay(Text(dropTargetText))
                .onDrop(of: [text], delegate: DTDropTarget(text: $text, dropTargetText: $dropTargetText))
            
            Button("Reset") {
                text = "DevTechie"
                dropTargetText = ""
            }
        }
        
    }
}


With that we have reached the end of this article. Thank you once again for reading. Subscribe to our weekly newsletter at https://www.devtechie.com