Reputation: 11427
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?
Upvotes: 3
Views: 1009
Reputation: 11427
Thanks a lot to answer of kakaiikaka!
The following solution works in swiftUI:
import Foundation
import SwiftUI
extension View {
func asDragable(url: URL, tapAction: @escaping () -> () , dTapAction: @escaping () -> ()) -> some View {
self.background {
DragDropView(url: url, tapAction: tapAction, dTapAction: dTapAction)
}
}
}
struct DragDropView: NSViewRepresentable {
let url: URL
let tapAction: () -> ()
let dTapAction: () -> ()
func makeNSView(context: Context) -> NSView {
return DragDropNSView(url: url, tapAction: tapAction, dTapAction: dTapAction)
}
func updateNSView(_ nsView: NSView, context: Context) { }
}
class DragDropNSView: NSView, NSDraggingSource {
let url: URL
let tapAction: () -> ()
let dTapAction: () -> ()
let imgMove: NSImage = NSImage(named: "arrow.down.doc.fill_cust")!
init(url: URL, tapAction: @escaping () -> (), dTapAction: @escaping () -> ()) {
self.url = url
self.tapAction = tapAction
self.dTapAction = dTapAction
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return mustBeMoveAction ? .move : .copy
}
}
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 event: NSEvent) {
super.mouseDown(with: event)
tapAction()
if event.clickCount == 2 {
dTapAction()
}
}
override func mouseDragged(with event: 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])
var rect = imgMove.alignmentRect
rect.size = NSSize(width: imgMove.size.width/2, height: imgMove.size.height/2)
//2. Creates a NSDraggingItem and assigns the pasteboard item to it
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(rect, contents: imgMove) // `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: event, source: self)
}
}
////////////////////////////////////////
///HELPERS
///////////////////////////////////////
extension DragDropNSView {
var dragGoingOutsideWindow: Bool {
guard let currEvent = NSApplication.shared.currentEvent else { return false }
if let rect = self.window?.contentView?.visibleRect,
rect.contains(currEvent.locationInWindow)
{
return false
}
return true
}
var mustBeMoveAction: Bool {
guard let currEvent = NSApplication.shared.currentEvent else { return false }
if currEvent.modifierFlags.check(equals: [.command]) {
return true
}
return false
}
}
extension NSEvent.ModifierFlags {
func check(equals: [NSEvent.ModifierFlags] ) -> Bool {
var notEquals: [NSEvent.ModifierFlags] = [.shift, .command, .control, .option]
equals.forEach{ val in notEquals.removeFirst(where: { $0 == val }) }
var result = true
equals.forEach{ val in
if result {
result = self.contains(val)
}
}
notEquals.forEach{ val in
if result {
result = !self.contains(val)
}
}
return result
}
}
usage:
FileIcon()
.asDragable( url: recent.url, tapAction: {}, dTapAction: {})
this element will be draggable and perform MOVE
in case .command
key pressed.
And will perform COPY
in another case
Also it performs drag action only outside widndow. But it's easy to change.
Upvotes: 2
Reputation: 4487
Remember Drag&Drop is actually implemented with NSPasteboard
.
I have written an example for you: GitHub
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).
Upvotes: 1