Мне нужно иметь возможность перетаскивать представление файла (в моем случае это pdf-файл) из NSView, содержащегося в моем приложении, на рабочий стол или другое приложение, которое поддерживает открытие файлов PDF.
Я провел несколько часовпытаясь заставить это работать в моем собственном приложении, и я подумал, что добавлю свое решение сюда, так как в сети много полу-решений, некоторые из которых основаны на расширениях Obj-C, а другие устарели и больше не поддерживаются. Я надеюсь, что эта статья будет такой, какую я бы хотел найти во время моих собственных поисков. Мне также известны все мелочи системы (например, использование файловых координаторов вместо прямой записи), но это, кажется, минимальный код, необходимый для реализации.
Я также предоставилпростая реализация Swift NSView.
Операция выполняется в три основных этапа.
Базовый обзор
Вам необходимо сделать свое представление (или другой элемент управления) «Поставщиком данных». для перетаскивания путем реализации протокола NSPasteboardItemDataProvider
. Большая часть требуемой работы (кроме запуска перетаскивания) выполняется в следующей функции протокола.
func pasteboard(_ pasteboard: NSPasteboard?, item _: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType)
Запуск перетаскивания
Этот раздел выполняется при запуске перетаскивания. В моем случае я делал это в mouseDown (), но вы также можете сделать это, например, в mouseDragged.
- Скажите монтажной панели, что мы предоставим тип файла UTI для отбрасывания (
kPasteboardTypeFilePromiseContent
) - Сообщите монтажной панели, что мы предоставим обещание файла (
kPasteboardTypeFileURLPromise
) для типа данных, указанного в (1)
Ответ получателю с запросом содержимогочто мы предоставим
kPasteboardTypeFilePromiseContent
Это первый обратный вызов от получателя отбрасывания (через pasteboard(pasteboard:item:provideDataForType:)
)
Получатель спрашивает нас, какой тип (UTI) файла мы предоставим. Ответьте, установив UTI (используя setString ("" на объекте монтажной панели) для типа
kPasteboardTypeFilePromiseContent
Ответ получателю о запросе файла
kPasteboardTypeFileURLPromise
Это второй обратный вызов от получателя (через pasteboard(pasteboard:item:provideDataForType:)
). Получатель просит насзаписать данные в файл на диске.
Получатель сообщает нам папку для записи нашего содержимого (
com.apple.pastelocation
) Записывает данные на диск внутри папки, о которой нам сообщил получатель. Ответьте, установиврезультирующий URL записанного файла (с помощью setString () на объекте из монтажной области) для типа
kPasteboardTypeFileURLPromise
. Обратите внимание, что формат этой строки должен быть
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)
}
}
Если кто-то обнаружит проблемы с этим или он совершенно неверный, пожалуйста, дайте мне знать! Похоже, это работает для моего приложения, по крайней мере.