Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active December 30, 2023 07:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JadenGeller/5edde65e388a7d00884069c5414c3ccc to your computer and use it in GitHub Desktop.
Save JadenGeller/5edde65e388a7d00884069c5414c3ccc to your computer and use it in GitHub Desktop.
NSFilePromiseProvider with SwiftUI, to drag a file that's loaded asynchronously
import SwiftUI
import UniformTypeIdentifiers
struct FileDragProvider: NSViewRepresentable {
var filePromise: FilePromise
var preview: PlatformImage
class NSViewType: NSView, NSFilePromiseProviderDelegate, NSDraggingSource {
var filePromise: FilePromise
var preview: PlatformImage
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
init(filePromise: FilePromise, preview: PlatformImage) {
self.filePromise = filePromise
self.preview = preview
super.init(frame: .zero)
}
var mouseDownLocation: CGPoint = .zero
override func mouseDown(with event: NSEvent) {
mouseDownLocation = event.locationInWindow
}
var hasDraggingSession = false
override func mouseDragged(with event: NSEvent) {
guard !hasDraggingSession else { return }
let distance = hypot(
event.locationInWindow.x - mouseDownLocation.x,
event.locationInWindow.y - mouseDownLocation.y
)
guard distance > 10 else { return }
let promise = NSFilePromiseProvider(fileType: filePromise.type.identifier, delegate: self)
let item = NSDraggingItem(pasteboardWriter: promise)
item.setDraggingFrame(.init(origin: .zero, size: frame.size), contents: preview)
beginDraggingSession(with: [item], event: event, source: self)
hasDraggingSession = true
}
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
.copy
}
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
hasDraggingSession = false
}
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
filePromise.name
}
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) {
Task.detached { [filePromise] in
do {
try await filePromise.writeToURL(url)
completionHandler(nil)
} catch let error {
completionHandler(error)
}
}
}
}
func makeNSView(context: Context) -> NSViewType {
NSViewType(filePromise: filePromise, preview: preview)
}
func updateNSView(_ nsView: NSViewType, context: Context) {
nsView.filePromise = filePromise
nsView.preview = preview
}
}
extension View {
func draggable(_ filePromise: FilePromise, preview: PlatformImage) -> some View {
overlay(FileDragProvider(filePromise: filePromise, preview: preview))
}
}
struct FilePromise {
var name: String
var type: UTType
var writeToURL: (URL) async throws -> Void
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment