Home > Software engineering >  SwiftUI macOS onDrop simple text always fails
SwiftUI macOS onDrop simple text always fails

Time:05-19

Trying to implement a simple drag-drop of a text file:

struct ContentView: View {
    
    @State private var drag = false
    
    var body: some View {
        Text("Hello, world!")
            .frame(width: 300, height: 300)
            .onDrop(of: [.url], isTargeted: $drag) { (providers, location) in
                
                
                print("Can load", providers.first?.canLoadObject(ofClass: NSURL.self) == true) // Always false
                
                providers.first?.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil, completionHandler: { secureCoding, error in
                    
                    if let error = error {
                        print(error) // Cannot load representation of type ...
                    }
                    
                    if let item = secureCoding as? Data { // Always nil
                        print("got data")
                    }
                })
                
                return true
            }
    }
}

Tried to play with both the UTType of the file dropped (public.url,public.data) and the loadItem(forTypeIdentifier). Nothing I tried worked. Found similar questions, but they're not working and the answers looks out dated.

I have old Cocoa code, with registerForDraggedTypes, this looks much simpler, if it worked..

CodePudding user response:

Just modified and tested with plain .txt file. Xcode 13.3 / macOS 12.3.1

    @State private var text = "Drop Here"
    var body: some View {
        Text(text)
            .frame(width: 200, height: 200).border(.red)
            .onDrop(of: ["public.file-url"], isTargeted: $dragOver) { providers -> Bool in
                providers.first?.loadDataRepresentation(forTypeIdentifier: "public.file-url", completionHandler: { (data, error) in
                    if let data = data, let path = NSString(data: data, encoding: 4), let url = URL(string: path as String) {
                        if let value = try? String(contentsOf: url, encoding: .utf8) {
                            DispatchQueue.main.async {
                                 self.text = value
                            }
                        }
                    }
                })
                return true
            }

*Note: I cannot remember exactly where but I met in Apple's documentation that we must use concrete UTTypes for specific use-cases instead of generic, ie. fileURL is really URL but for matching it must be specified exactly!

CodePudding user response:

I wanted to post my own answer, just to point out the pitfalls. Thanks to @Asperi, I got a working sample.

  1. .onDrop(of: [.fileURL], needs to be UTType.fileURL (or public.file-url), not UTType.url.
  2. I was using providers.first?.loadItem(forTypeIdentifier:..., needs to be loadDataRepresentation(forTypeIdentifier:...
  3. Reading the data:
    Using let path = String(data: data, encoding: .utf8) works. The next step is important, I needed to use URL(string:). not URL(fileURLWithPath:)
    Using URL(fileURLWithPath:) will produce something that looks like URL with secure scope.

Other minor notes:
Using let path = String(data: data, encoding: .utf8) works, no need for NNString.
Using UTType.fileURL (and UTType.fileURL.identifier for String) also works, no need for hard coding "public.file-url"

  • Related