【问题标题】:NSItemProvider[URL] - how to COPY with drag&Drop instead of MOVE?NSItemProvider[URL] - 如何使用拖放而不是 MOVE 进行复制?
【发布时间】:2022-09-22 22:35:15
【问题描述】:

我已经实现了返回 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()
}

我已经这样使用它了:

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

此拖放操作执行文件 MOVE 操作到任何位置查找器/硬盘。

但是如何执行 COPY 动作呢?

  • AppKit 的NSDragOperation 有用吗?

标签: swift macos swiftui nsitemprovider


【解决方案1】:

记住 Drag&Drop 实际上是用NSPasteboard 实现的。

我为你写了一个例子: GitHub

现在是您问题的关键:

要控制拖动行为(您的窗口是源):

可拖动对象符合NSDraggingSource协议,所以检查协议的第一种方法:

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

作为method docsuggests,在这个委托方法中返回不同的NSDragOperation。这包括:“复制”、“移动”、“链接”等。

要控制放置行为(您的窗口是目标):

接受 drop 的 NSView 符合 NSDraggingDestination 协议,因此您需要通过在 Destination 中添加此代码来覆盖 draggingEntered(_:) 方法看法类实现:

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

更多信息表格Apple's Documentation

对于swiftUI,一个简单的案例SwiftUI Showcase

延伸阅读: RayWenderlich.com 为您提供了详细的教程Drag and Drop Tutorial for macOS 教程(需要快速升级)。

【讨论】:

  • 目的地是查找器或任何其他应用程序......所以我不能覆盖目的地中的方法。读取函数名称:“dragOutsideWnd”或“此拖放操作执行文件 MOVE 操作到 FINDER/HDD 的任何位置”有问题:) 但感谢您的回复
  • 我认为 Appkit 解决方案也可以。但我需要将 url 从我的应用程序拖动(复制)到外部应用程序
  • @Andrew___Pls_Support_UA,我编辑了答案,在你的情况下是 draggingSession( _ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext ) -> NSDragOperation 方法
  • 谢谢,明天会检查你的答案,非常感谢!
  • 为您创建了一个 github 示例。看看吧,兄弟!
【解决方案2】:

非常感谢kakaiikaka的回答!

以下解决方案适用于 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
    }
}

用法:

FileIcon()
    .asDragable( url: recent.url, tapAction: {}, dTapAction: {})

这个元素将是可拖动的,并且在按下.command 键的情况下执行MOVE

并在另一种情况下执行COPY

它也只在窗口外执行拖动动作。但是很容易改变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    • 2020-03-09
    • 2010-11-22
    • 2021-06-09
    • 2018-08-02
    相关资源
    最近更新 更多