Drag And Drop in SwiftUI

In this post, we’ll explore how to use Drag & Drop building blocks to create a fun and educational game! Learn how to make players sort trash into the correct bins, just like in the video below:

The goal is to drag and drop items into the correct bins. Newspapers will disappear from the list when placed in the recycling bin, while plastic and glass will be rejected.

The Data

Let’s start with the defition of the data of the element that we want drag:

struct ImageItem: Identifiable {
    var id = UUID()
    var name: String
    var image: String
}

@Observable
class Images  {
    var items: Array<ImageItem> = Array<ImageItem>()
 
    init() {
        items = [
            ImageItem(name: "newspaper", image: "newspaper"),
            ImageItem(name: "plastic", image: "plastic"),
            ImageItem(name: "glass", image: "glass")
        ]
    }
}

We define a struct that includes the name of the element, the image of the element, and a unique ID. After that, we define a class (which is defined as Observable, and is valid starting from iOS 17) that contains the list of elements we want to drag. It’s marked as observable because we need to monitor changes within it.

Visualization

The graphic visualization of the ImageItem is this:

struct ImageUIView: View {
    var imageItem: ImageItem
 
    var body: some View {
        VStack{
            Image(imageItem.image)
                .resizable()
                .frame(width: 150, height: 150)
        }
        .cornerRadius(8)
    }
}

The graphical visualization of the ImageItem is straightforward: it’s a simple image with a fixed size. Additionally, I’ve incorporated a VStack for those who wish to add a text description for the image.

Actions

Now take a look to the main part:

sstruct ContentView: View {
    @State private var images = Images()
    @State private var draggingItem: ImageItem?
    
    var body: some View {
        HStack {
            VStack {
                ForEach(images.items, id: \.id) { imageItem in
                    ImageUIView(imageItem: imageItem)
                        .onDrag({
                            draggingItem = imageItem
                            return NSItemProvider() })
                }
            }
            .frame(width: 150)
            .frame(maxHeight: 150)
            .padding(.leading)

            Spacer()
            
            VStack {
                Image(systemName: "trash")
                .resizable()
                .frame(width: 280, height: 220)
                .background(Color.gray.opacity(0.25))
                .border(.yellow, width: 1)
                .padding(.trailing)
                .onDrop(of: [.text], delegate: DropViewDelegate(items:$images.items, draggedItem: $draggingItem))
            }
        }
    }
}

We define the variables for the images specified in the previously defined class, along with a draggingItem, an optional variable that holds the element we are currently dragging. When we invoke the onDrag method, draggingItem gets set, and the closure returns an NSItemProvider(). This is described in the Apple documentation as follows: “An item provider for conveying data or a file between processes during drag-and-drop or copy-and-paste activities, or from a host app to an app extension”.

In the onDrop method, we specify the types that we want to consider, in this case, text, along with a delegate. This involves using the list of items and the draggingItem as follows:

struct DropViewDelegate: DropDelegate {
    @Binding var items: [ImageItem]
    @Binding var draggedItem: ImageItem?
    
    func performDrop(info: DropInfo) -> Bool {
        items.remove(at: 0)
        return true
    }
    
    func validateDrop(info: DropInfo) -> Bool {
        if let draggedItem = draggedItem, draggedItem.name == "newspaper" {
            return true
        }
        return false
    }
}

The first function to be called is validateDrop. In this instance, it checks the name of the image, so the drop action is validated/accepted based on the value of the name.

If the validation is successful, the drop action is performed by executing the code within performDrop. In this example (to keep it brief), we remove the first element of the list, which is the newspaper. In a real-world scenario, we could apply a filter to the items.

Enjoy creating your game.

Note: English is not my native language, sorry for any errors. I use AI only to generate the banner of the post, the content is human.

share this post with friends

Picture of Nicola De filippo

Nicola De filippo

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve

Leave a comment

Your email address will not be published. Required fields are marked *

Who I am

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve.

Follow Me Here

Get The Latest Updates

Periodically receive my super contents on coding and programming

join the family;)

Recent Posts