09
            Oct
            2022
        
                            
    SwiftUI Drag & Drop
Reading time: 2 min
This recipe shows how to implement drag & drop functionality in SwiftUI.
The end result looks like this:

This code works starting with SwiftUI 3 (iOS 15, macOS 12).
Drag & drop in SwiftUI works somewhat out of the box, as long as you're acquanited with UTTypes you wish to handle. The recipe goes like this:
- The draggable view should have a 
onDragmodifier to provide aNSItemProviderinstance. This wrapper represents the data you want to provide to the drop location.- You can optionally provide a custom 
preview, which will be shown during the drag. 
 - You can optionally provide a custom 
 - The drop location should have a 
onDropmodifier. There, you need to provide:- List of valid 
UTTypes for this drop location. Only drags that contain valid data will be accepted in the drop handler. - Either a 
DropDelegatereference or a handler that is called when the drop happens and allows you to make use of the droppedNSItemProviders. Additionally, if you use the handler variant, you can specify aBoolbinding that indicates if there's an active drop above the location or not. The handler variant is all you need most of the time. 
 - List of valid 
 
Here's some code that shows all of that in action:
import SwiftUI
import UniformTypeIdentifiers
struct DragDropTest: View {
  @State private var textInput = ""
  @State private var items = ["Apple", "Orange", "Kiwi", "Pear"]
  @State private var dragInProgress = false
  var body: some View {
    VStack(alignment: .leading) {
      TextField("New fruit", text: $textInput)
        .onDrag {
          NSItemProvider(object: textInput as NSString)
        } preview: {
          Label("Add new fruit!", systemImage: "applelogo")
        }
      Text("Fruit")
        .padding(.top, 30)
      HStack {
        ForEach(items, id: \.self) { fruit in
          Text(fruit)
        }
      }
      .background(dragInProgress ? Color.orange : nil)
      .onDrop(of: [UTType.plainText], isTargeted: $dragInProgress) { providers in
        for item in providers {
          item.loadObject(ofClass: NSString.self) { item, error in
            if let str = item as? String {
              items.append(str)
            }
          }
        }
        return true
      }
    }
  }
}
For the sake of completeness, here's the same code, but using DropDelegate:
import SwiftUI
import UniformTypeIdentifiers
struct DragDropTest: View, DropDelegate {
  @State private var textInput = ""
  @State private var items = ["Apple", "Orange", "Kiwi", "Pear"]
  @State private var dragInProgress = false
  var body: some View {
    VStack(alignment: .leading) {
      TextField("New fruit", text: $textInput)
        .onDrag {
          NSItemProvider(object: textInput as NSString)
        } preview: {
          Label("Add new fruit!", systemImage: "applelogo")
        }
      Text("Fruit")
        .padding(.top, 30)
      HStack {
        ForEach(items, id: \.self) { fruit in
          Text(fruit)
        }
      }
      .background(dragInProgress ? Color.orange : nil)
      .onDrop(of: [UTType.plainText], delegate: self)
      Spacer()
    }
    .padding()
  }
  func dropEntered(info: DropInfo) {
    dragInProgress = info.hasItemsConforming(to: [UTType.plainText])
  }
  func dropExited(info: DropInfo) {
    dragInProgress = false
  }
  func performDrop(info: DropInfo) -> Bool {
    for item in info.itemProviders(for: [UTType.plainText]) {
      item.loadObject(ofClass: NSString.self) { item, error in
        if let str = item as? String {
          items.append(str)
        }
      }
    }
    dragInProgress = false
    return true
  }
}
The drag functionality doesn't work in XCode previews - you need to run the code on either a simulator or a real device.