3

I'm trying to implement simple list with draggable cells (iOS 15). For now I have two base solutions: List and ScrollView. None of this methods doesn't lead me to appropriate result.

First of all I'll provide some code:

List item view:

struct ListItemView: View {
    let index: Int

    var body: some View {
        HStack {
            Text("Item \(index)")
                .foregroundColor(.white)
            Spacer()
        }
        .padding()
        .background(Color.gray)
        .clipShape(RoundedRectangle(cornerRadius: 16.0))
    }
}

First solution (using List):

struct ContentView: View {
    var body: some View {
        List(0 ..< 10) { i in
            ListItemView(index: i)
                .onDrag {
                    NSItemProvider(object: String(describing: "item_\(i)") as NSString)
                }
                .padding(.bottom, 8.0)
                .listRowBackground(Color.clear)
                .listRowSeparator(.hidden)
                .listRowInsets(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8))
        }
        .background(Color.black)
        .listStyle(PlainListStyle())
    }
}

The result is fine, but when I start dragging action, I see some white rect around my cell, which is not what I want:

Drag&Drop preview using ScrollView

Second solution (using ScrollView)

struct ContentView: View {
    var body: some View {
        ScrollView {
            ForEach(0 ..< 10) { i in
                ListItemView(index: i)
                    .onDrag {
                        NSItemProvider(object: String(describing: "item_\(i)") as NSString)
                    }
            }
        }
        .background(Color.black)
    }
}

The result is a bit different: we don't see any coloured rects (I suppose, this is because we are not using List and there is different mechanism of drag&drop feature). Also there is scaled preview and shape without rounded corners.

Drag&Drop preview using List

So, the desirable behaviour is: a) the size of preview is the same as original size of list item b) there is rounded-corners shape without white frame

How can I achieve it?

agoncharov
  • 822
  • 1
  • 6
  • 15

1 Answers1

1

Theoretically your NSItemProvider can offer a previewImageHandler which is a block that draws the image you want the system to use as the drag preview. Unfortunately that doesn't seem to work. The documentation of the onDrag modifier says that it uses a picture of the view as the preview image and it seemingly ignores the previewImageHandler. If someone finds out otherwise, leave a comment and I'll amend the answer. Here's what I tried:

import SwiftUI

struct ListItem : Identifiable {
    let id: Int
    let title: String
}

let itemsInList = (0..<10).map { ListItem(id: $0, title: "Item \($0)") }

@ViewBuilder func ListItemView(title : String) -> some View {
    ZStack(alignment: .leading) {
        RoundedRectangle(cornerRadius: 18, style: .circular)
            .foregroundColor(Color(red: 142.0/255.0, green: 142.0/255.0, blue: 142.0/255.0, opacity: 1.0))
        Text(title)
            .foregroundColor(.white)
            .padding()
    }
}

struct ContentView: View {
    var body: some View {
        List(itemsInList) {
            let itemTitle = $0.title
            ListItemView(title: itemTitle)
                .onDrag {
                    let itemProvider = NSItemProvider()
                    itemProvider.registerObject("Item \(itemTitle)" as NSString, visibility: .all)
                    itemProvider.previewImageHandler = {
                        (completionHandler, expectedClass, options) -> Void in

                        var resultImage : CGImage?

                        debugPrint(expectedClass ?? "Unknown")
                        if let cgContext = CGContext(data: nil,
                                                     width: 72,
                                                     height: 72,
                                                     bitsPerComponent: 8,
                                                     bytesPerRow: 16 * (((72 * 4) + 15) / 16),
                                                     space: CGColorSpace(name: CGColorSpace.sRGB)!,
                                                     bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) {

                            let frame = CGRect(x: 0, y: 0, width: 72, height: 72)
                            cgContext.clear(frame)
                            cgContext.setFillColor(UIColor.red.cgColor)
                            cgContext.fillEllipse(in: frame)

                            resultImage = cgContext.makeImage()
                        }

                        if let completionHandler = completionHandler {
                            if let resultImage = resultImage {
                                completionHandler(UIImage(cgImage: resultImage), nil)
                            } else {
                                completionHandler(nil, NSError(domain: "ItemCompletion", code: -1, userInfo: nil))
                            }
                        }
                    }

                    return itemProvider
                }
        }
    }
}


struct previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • Hey, Scott, thank you for your answer! I tried to implement this handler for my `NSItemProvider`, but it isn't being called (I suppose, iOS should manage this by itself). The only way I get this – calling `loadPreviewImage` method by myself. Could you please provide some more details about how to use this handler? P.S. I found similar question about `previewImageHandler`, but sadly, there are no any answer or example at all... https://stackoverflow.com/questions/67248900/how-can-i-set-the-dragging-image-in-swiftui-on-macos – agoncharov Sep 29 '21 at 14:07
  • I could not get a sample to work either, but I put what I tried above. I'm going to send this in as a bug report – Scott Thompson Oct 02 '21 at 02:37