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.