Issue #951
Create a custom NSView
that handles mouseDragged
to beginDraggingSession
struct DraggingSourceViewWrapper: NSViewRepresentable {
let fileUrls: [URL]
let onEnd: () -> Void
func makeNSView(context: Context) -> DraggingSourceView {
let view = DraggingSourceView(fileUrls: fileUrls)
view.onEnd = onEnd
return view
}
func updateNSView(_ view: DraggingSourceView, context: Context) {
view.onEnd = onEnd
}
}
final class DraggingSourceView: NSView {
let fileUrls: [URL]
var onEnd: (() -> Void)?
init(fileUrls: [URL]) {
self.fileUrls = fileUrls
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError()
}
override func mouseDown(with event: NSEvent) {
// NOTE: Needed for mouseDragged to work
}
override func mouseDragged(with event: NSEvent) {
let draggingItems = fileUrls.map { url in
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setString(url.absoluteString, forType: .string)
pasteboardItem.setDataProvider(self, forTypes: [.fileURL])
let image = NSWorkspace.shared.icon(forFile: url.path)
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(bounds, contents: image)
return draggingItem
}
beginDraggingSession(with: draggingItems, event: event, source: self)
}
}
extension DraggingSourceView: NSDraggingSource {
func draggingSession(
_ session: NSDraggingSession,
sourceOperationMaskFor context: NSDraggingContext
) -> NSDragOperation {
switch context {
case .outsideApplication:
return .move
default:
return .private
}
}
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
onEnd?()
}
}
extension DraggingSourceView: NSPasteboardItemDataProvider {
nonisolated func pasteboard(
_ pasteboard: NSPasteboard?,
item: NSPasteboardItem,
provideDataForType type: NSPasteboard.PasteboardType
) {
guard
let string = item.string(forType: .string),
let url = URL(string: string)
else { return }
if let url = BookmarkService.shared.resolve(url: url) {
url.access {
let data = url.absoluteString.data(using: .utf8) ?? Data()
item.setData(data, forType: .fileURL)
}
}
}
}