【问题标题】:Drag a file promise from a NSView onto the desktop or another application (macOS)将文件承诺从 NSView 拖到桌面或其他应用程序 (macOS)
【发布时间】:2019-11-03 08:36:39
【问题描述】:

我需要能够将文件表示(在我的情况下为 pdf)从我的应用程序中包含的 NSView 拖到桌面或其他支持打开 PDF 文件的应用程序上。

【问题讨论】:

标签: swift macos drag-and-drop nsview


【解决方案1】:

我花了几个小时试图让它在我自己的应用程序中运行,我想我应该在这里添加我的解决方案,因为网上有很多半解决方案,其中一些依赖于 Obj-C 扩展,而另一些依赖于已过时,不再受支持。我希望这篇文章是我希望在我自己的搜索中找到的那种帖子。我也知道系统的所有细节(例如,使用文件协调器而不是直接写入),但这似乎是实现所需的最少代码。

我还提供了一个简单的 Swift NSView 实现。

操作分为三个主要阶段。

基本概述

您需要通过实现NSPasteboardItemDataProvider 协议使您的视图(或其他控件)成为拖动的“数据提供者”。所需的大部分工作(开始拖动除外)发生在以下协议函数中。

func pasteboard(_ pasteboard: NSPasteboard?, item _: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType)

开始拖动

此部分在拖动开始时出现。就我而言,我在 mouseDown() 中执行此操作,但您也可以在 mouseDragged 中执行此操作。

  1. 告诉粘贴板我们将为 drop 提供文件类型 UTI (kPasteboardTypeFilePromiseContent)
  2. 告诉粘贴板我们将为 (1) 中指定的数据类型提供文件承诺 (kPasteboardTypeFileURLPromise)

回应接收者询问我们将提供的内容

kPasteboardTypeFilePromiseContent

这是来自 drop 接收者的第一个回调(通过 pasteboard(pasteboard:item:provideDataForType:)

  1. 接收方询问我们将提供哪种类型 (UTI) 的文件。
  2. 通过设置 kPasteboardTypeFilePromiseContent 类型的 UTI(在粘贴板对象上使用 setString(""))进行响应

响应接收者请求文件

kPasteboardTypeFileURLPromise

这是接收方的第二次回调(通过pasteboard(pasteboard:item:provideDataForType:)) 接收方要求我们将数据写入磁盘上的文件。

  1. 接收方告诉我们要将内容写入的文件夹 (com.apple.pastelocation)
  2. 将数据写入接收方告诉我们的文件夹内的磁盘。
  3. 通过为类型kPasteboardTypeFileURLPromise 设置写入文件的结果URL(使用粘贴板对象上的setString())来响应。注意,这个字符串的格式需要是file:///...,所以需要使用.absoluteString()

我们完成了!

示例


// Some definitions to help reduce the verbosity of our code
let PasteboardFileURLPromise = NSPasteboard.PasteboardType(rawValue: kPasteboardTypeFileURLPromise)
let PasteboardFilePromiseContent = NSPasteboard.PasteboardType(rawValue: kPasteboardTypeFilePromiseContent)
let PasteboardFilePasteLocation = NSPasteboard.PasteboardType(rawValue: "com.apple.pastelocation")

class MyView: NSView {
   override func mouseDown(with event: NSEvent) {
      let pasteboardItem = NSPasteboardItem()

      // (1, 2) Tell the pasteboard item that we will provide both file and content promises
      pasteboardItem.setDataProvider(self, forTypes: [PasteboardFileURLPromise, PasteboardFilePromiseContent])

      // Create the dragging item for the drag operation
      let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
      draggingItem.setDraggingFrame(self.bounds, contents: image())

      // Start the dragging session
      beginDraggingSession(with: [draggingItem], event: event, source: self)
   }
}

然后,在您的粘贴板项目数据提供程序扩展中...

extension MyView: NSPasteboardItemDataProvider {
   func pasteboard(_ pasteboard: NSPasteboard?, item _: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType) {

   if type == PasteboardFilePromiseContent {

      // The receiver will send this asking for the content type for the drop, to figure out
      // whether it wants to/is able to accept the file type (3).
      // In my case, I want to be able to drop a file containing PDF from my app onto
      // the desktop or another app, so, add the UTI for the pdf (4).

      pasteboard?.setString("com.adobe.pdf", forType: PasteboardFilePromiseContent)
   }
   else if type == PasteboardFileURLPromise {

      // The receiver is interested in our data, and is happy with the format that we told it
      // about during the kPasteboardTypeFilePromiseContent request. 
      // The receiver has passed us a URL where we are to write our data to (5).
      // It is now waiting for us to respond with a kPasteboardTypeFileURLPromise

      guard let str = pasteboard?.string(forType: PasteboardFilePasteLocation),
       let destinationFolderURL = URL(string: str) else {
         // ERROR:- Receiver didn't tell us where to put the file?
         return
      }

      // Here, we build the file destination using the receivers destination URL
      // NOTE: - you need to manage duplicate filenames yourself!
      let destinationFileURL = destinationFolderURL.appendingPathComponent("dropped_file.pdf")

      // Write your data to the destination file (6). Do better error handling here!
      let pdfData = self.dataWithPDF(inside: self.bounds)
      try? pdfData.write(to: destinationFileURL, options: .atomicWrite)

      // And finally, tell the receiver where we wrote our file (7)
      pasteboard?.setString(destinationFileURL.absoluteString, forType: PasteboardFileURLPromise)
   }
}

如果有人发现此问题或完全不正确,请告诉我!它似乎至少适用于我的应用程序。


正如 Willeke 所指出的,Apple 有一些示例代码用于使用(较新的)NSFilePromiseProvider 机制进行拖放。

https://developer.apple.com/documentation/appkit/documents_files_and_icloud/supporting_drag_and_drop_through_file_promises

我希望我的搜索从 Apple 的开发者页面而不是 Google ? 开始。那好吧!提供的示例是有效的并且仍然有效,所以如果这篇文章可以帮助某人找到有关拖放的更具凝聚力的信息,那就太棒了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 2010-11-15
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 2012-12-27
    • 2016-02-20
    相关资源
    最近更新 更多