Skip to main content

How to support drag and drop in SwiftUI

About 4 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to support drag and drop in SwiftUI 관련

SwiftUI by Example

Back to Home

How to support drag and drop in SwiftUI | SwiftUI by Example

How to support drag and drop in SwiftUI

Updated for Xcode 15

Improved in iOS 16

SwiftUI's Transferable protocol allows us to add drag and drop support to our apps without a great deal of code, using the dropDestination() and draggable() modifiers.

For example, this will accept text dragged into our app, and render it onto a Canvas:

struct ContentView: View {
    @State private var message = ""

    var body: some View {
        Canvas { context, size in
            let formattedText = Text(message).font(.largeTitle).foregroundStyle(.red)
            context.draw(formattedText, in: CGRect(origin: .zero, size: size))
        }
        .dropDestination(for: String.self) { items, location in
            message = items.first ?? ""
            return true
        }
    }
}

Download this as an Xcode projectopen in new window

The key part there is the dropDestination() modifier, which tells SwiftUI four things:

  1. We accept only strings.
  2. We expect to receive an array of items that were dropped on the app. This will automatically be an array of String.
  3. We want to be told where they were dropped. This will be a CGPoint in the canvas's coordinate space.
  4. We consider the drop operation to be successful, so we return true.

Handling images is a little trickier because we'll be given a Data instance representing the contents of the image. We need to convert that to a UIImage, and put the result into an image to render.

Fortunately, if we support Data then both will work, so code like this lets the user drag an image from Photos right into our app:

struct ContentView: View {
    @State private var image = Image(systemName: "photo")

    var body: some View {
        image
            .resizable()
            .scaledToFit()
            .frame(width: 300, height: 300)
            .dropDestination(for: Data.self) { items, location in
                guard let item = items.first else { return false }
                guard let uiImage = UIImage(data: item) else { return false }
                image = Image(uiImage: uiImage)
                return true
            }

    }
}

Download this as an Xcode projectopen in new window

Accepting arrays of data – for example letting the user drag multiple images into our – follows the same procedure: using dropDestination(for: Data.self), but now rather than just reading the first item you should use them all.

For example, this shows several pictures in a ScrollView by converting each Data instance into a UIImage, and then into a SwiftUI Image:

struct ContentView: View {
    @State private var images = [Image]()

    var body: some View {
        ScrollView {
            VStack {
                ForEach(0..<images.count, id: \.self) { i in
                    images[i]
                        .resizable()
                        .scaledToFit()
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .dropDestination(for: Data.self) { items, location in
            images = items.compactMap {
                UIImage(data: $0).map(Image.init)
            }

            return true
        }
    }
}

Download this as an Xcode projectopen in new window

When you want to add dragging to your views, add the draggable() modifier using whatever Transferable content you want. By default SwiftUI will use the view itself for the drag preview, and if you're dragging an image from within your app you'll find you can use the drop type of Image.self rather than having to convert Data to UIImage first.

For example, this shows three different SF Symbols and lets the user drag any of them into the box below:

struct ContentView: View {
    let sports = ["figure.badminton", "figure.cricket", "figure.fencing"]
    @State private var dropImage = Image(systemName: "photo")

    var body: some View {
        VStack {
            HStack {
                ForEach(sports, id: \.self) { sport in
                    Image(systemName: sport)
                        .frame(minWidth: 50, minHeight: 50)
                        .background(.red)
                        .foregroundStyle(.white)
                        .draggable(Image(systemName: sport))
                }
            }
            .frame(minWidth: 300, minHeight: 70)
            .background(.gray)

            dropImage
                .frame(width: 150, height: 150)
                .background(.green)
                .foregroundStyle(.white)
                .dropDestination(for: Image.self) { items, location in
                    dropImage = items.first ?? Image(systemName: "photo")
                    return true
                }
        }
    }
}

Download this as an Xcode projectopen in new window

Important

When you're dragging an SF Symbol image, SwiftUI will send the image pixel data and not the neatly scalable vector we're used to. This means dropped Image data won't respond to things like font() or foregroundStyle() like you might expect.

If you want to show a custom drag preview, add a trailing closure with some SwiftUI views. For example, this makes a draggable golf image and adds the text “Figure playing golf” next to it:

Image(systemName: "figure.golf")
    .frame(minWidth: 50, minHeight: 50)
    .background(.red)
    .foregroundStyle(.white)
    .draggable(Image(systemName: "figure.golf")) {
        Label("Figure playing golf", systemImage: "figure.golf")
    }

Download this as an Xcode projectopen in new window

Similar solutions…
SwiftUI tips and tricks | SwiftUI by Example

SwiftUI tips and tricks
All SwiftUI property wrappers explained and compared | SwiftUI by Example

All SwiftUI property wrappers explained and compared
How to use Instruments to profile your SwiftUI code and identify slow layouts | SwiftUI by Example

How to use Instruments to profile your SwiftUI code and identify slow layouts
Answering the big question: should you learn SwiftUI, UIKit, or both? | SwiftUI by Example

Answering the big question: should you learn SwiftUI, UIKit, or both?
Frequently asked questions about SwiftUI | SwiftUI by Example

Frequently asked questions about SwiftUI

이찬희 (MarkiiimarK)
Never Stop Learning.