-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
227: Practice Exercise: Building a Drag-and-Drop Interface in SwiftUI
A few years ago, I was reviewing a PR for a junior dev who was trying to build a custom Trello-style kanban board. He had spent three days fighting with DragGesture, manually calculating offsets and trying to figure out which list item the user was hovering over by comparing global coordinates. It was a nightmare to read, and it felt "jittery" on the screen. I told him to scrap it all and look at the native Transferable APIs. He was skeptical—he thought native drag-and-drop was only for moving files between apps—but once he saw how SwiftUI handles the data handoff, he finished the feature in an afternoon.
The trick is to stop thinking about the *movement* of the view and start thinking about the *transfer* of data. In SwiftUI, we don't move the UI element itself; we tell the system, "This piece of data is now floating," and then we tell a destination, "If you see this type of data, here is how to handle it."
Defining the Transferable Payload
To make an element draggable, it has to conform to the Transferable protocol. This is where most people get stuck because they try to make the entire View draggable. You don't do that. Instead, you make your data model transferable. If you're moving a Task object, that Task needs to know how to represent itself as something the system can move—usually a string or a custom data blob.
struct TodoTask: Identifiable, Codable, Transferable {
let id: UUID
var title: String
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation()
}
}
By using CodableRepresentation, you're telling SwiftUI to handle the serialization for you. Once your model is set up, adding the .draggable() modifier to your view is a one-liner. I usually recommend adding a slight visual cue, like a subtle shadow or scale effect, so the user knows the item is "lifted," though the system provides a default ghost image automatically.
Handling the Drop Destination
Now that you've got data floating in the ether, you need a place for it to land. The .dropDestination modifier is your best friend here. It requires two things: the type of data you're expecting and a closure that executes when the drop happens.
Here is the part where you'll actually update your state. When the drop occurs, the closure gives you the transferred item. You then find that item in your source list, remove it, and append it to the destination list. I've found that if you don't wrap this logic in withAnimation, the transition feels abrupt and robotic. Always animate your state changes during a drop; it's the difference between an app that feels "engineered" and one that feels "polished."
.dropDestination(for: TodoTask.self) { items, location in
guard let task = items.first else { return false }
withAnimation(.spring()) {
// Logic to move task from 'todoList' to 'doneList'
todoList.removeAll { $0.id == task.id }
doneList.append(task)
}
return true
}
One quick tip: keep your drop zones large. Nothing frustrates a user more than trying to drop an item into a column and having it "bounce back" because they missed the target by two pixels. Make the entire container the drop destination, not just the individual cells.
📋 Practical Task
Exercise: The Kanban Task Shifter
Build a simple two-column task organizer. You will need to implement the following:
- Create a
TaskItemstruct that conforms toIdentifiable,Codable, andTransferable. - Set up two arrays of
TaskItem(e.g.,todoTasksandcompletedTasks) inside aViewModelor as@Statevariables. - Create a view with two
VStackcolumns. Each column should display its respective list of tasks. - Apply the
.draggable()modifier to the task rows so they can be picked up. - Apply the
.dropDestination()modifier to each column. When a task is dropped into the "Completed" column, it should be removed from the "Todo" list and added to the "Completed" list (and vice versa). - Ensure the movement is wrapped in a
withAnimationblock to provide a smooth visual transition.
There are no comments for now.