Home > Software design >  NSItemProvider[URL] - how to COPY with drag&Drop instead of MOVE?
NSItemProvider[URL] - how to COPY with drag&Drop instead of MOVE?

Time:09-21

I have implemented function that returns NSItemProvider

func dragOutsideWnd(url: URL?) -> NSItemProvider {
    if let url = url {
        TheApp.appDelegate.hideMainWnd()
        
        let provider = NSItemProvider(item: url as NSSecureCoding?, typeIdentifier: UTType.fileURL.identifier as String)
        
        provider.suggestedName = url.lastPathComponent
        //provider.copy()// This doesn't work :)
        
        //DispatchQueue.main.async {
        //    TheApp.appDelegate.hideMainWnd()
        //}
        
        return provider
    }
    
    return NSItemProvider()
}

and I have use it this way:

.onDrag {
   return dragOutsideWnd(url: itm.url)
}

This drag&drop action performs file MOVE action to any place of FINDER/HDD.

But how to perform COPY action?

CodePudding user response:

Remember Drag&Drop is actually implemented with NSPasteboard.

I have written an example for you: enter image description here

Now the key to your questions:

To control dragging behavior(your window is the source):

Draggable objects conform to the NSDraggingSource protocol, so check the first method of the protocol:

@MainActor func draggingSession(
    _ session: NSDraggingSession,
    sourceOperationMaskFor context: NSDraggingContext
) -> NSDragOperation

As the method docsuggests, return different NSDragOperation in this delegation method. That includes: "Copy","Move", "Link", etc.

To control dropping behavior(your window is the destination):

NSView that accepts drop conforms to the NSDraggingDestination protocol, so you need to override the draggingEntered(_:) method by adding this code inside the DestinationView class implementation:

override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation 
{
   var allow = true
   //.copy .move, see more options in NSDragOperation, up to you.
   return allow ? .copy : NSDragOperation() 
}

More info form Apple's Documentation

For swiftUI, a simple show case SwiftUI Showcase

Further Reading: RayWenderlich.com has a detailed tutorial Drag and Drop Tutorial for macOS tutorial for you(needs a little swift upgrade).

CodePudding user response:

Thanks a lot to answer of kakaiikaka!

The following solution works in swiftUI:

import Foundation
import SwiftUI

extension View {
    func asDragable(_ oper: NSDragOperation, url: URL) -> some View {
        self.overlay {
            DragDropView(url: url, oper: oper)
        }
    }
}

struct DragDropView: NSViewRepresentable {
    let url: URL
    let oper: NSDragOperation
    
    func makeNSView(context: Context) -> NSView {
        return DragDropNSView(url: url, oper: oper)
    }
    
    func updateNSView(_ nsView: NSView, context: Context) { }
}


class DragDropNSView: NSView, NSDraggingSource {
    let url: URL
    let oper: NSDragOperation
    
    init(url: URL, oper: NSDragOperation) {
        self.url = url
        self.oper = oper
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
        return oper
    }
}

extension DragDropNSView: NSPasteboardItemDataProvider {
    func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType) {
        // If the desired data type is fileURL, you load an file inside the bundle.
        if let pasteboard = pasteboard, type == NSPasteboard.PasteboardType.fileURL {
            pasteboard.setData(url.dataRepresentation, forType:type)
        }
    }
    
    override func mouseDown(with theEvent: NSEvent) {
        //1. Creates an NSPasteboardItem and sets this class as its data provider. A NSPasteboardItem is the box that carries the info about the item being dragged. The NSPasteboardItemDataProvider provides data upon request. In this case a file url
        let pasteboardItem = NSPasteboardItem()
        pasteboardItem.setDataProvider(self, forTypes: [NSPasteboard.PasteboardType.fileURL])
        
        //2. Creates a NSDraggingItem and assigns the pasteboard item to it
        let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
        draggingItem.setDraggingFrame(self.bounds, contents: NSImage(named: "Preview")) // `contents` is the preview image when dragging happens.
        
        //3. Starts the dragging session. Here you trigger the dragging image to start following your mouse until you drop it.
        beginDraggingSession(with: [draggingItem], event: theEvent, source: self)
    }
}

usage:

FileIcon()
    .asDragable(.copy, url: URL(fileURLWithPath: "/Users/uks/Desktop/mbrgx_fpvasffujdfjhymdq8yfo.png"))

FileIcon()
    .asDragable(.move, url: URL(fileURLWithPath: "/Users/uks/Desktop/mbrgx_fpvasffujdfjhymdq8yfo.png"))
  • Related