import AppKit import CoreTransferable import UniformTypeIdentifiers extension Transferable { /// Converts this Transferable to an NSPasteboardItem with lazy data loading. /// Data is only fetched when the pasteboard consumer requests it. This allows /// bridging a Transferable to NSDraggingSource. func pasteboardItem() -> NSPasteboardItem? { let itemProvider = NSItemProvider() itemProvider.register(self) let types = itemProvider.registeredTypeIdentifiers.compactMap { UTType($0) } guard !types.isEmpty else { return nil } let item = NSPasteboardItem() let dataProvider = TransferableDataProvider(itemProvider: itemProvider) let pasteboardTypes = types.map { NSPasteboard.PasteboardType($0.identifier) } item.setDataProvider(dataProvider, forTypes: pasteboardTypes) return item } } private final class TransferableDataProvider: NSObject, NSPasteboardItemDataProvider { private let itemProvider: NSItemProvider init(itemProvider: NSItemProvider) { self.itemProvider = itemProvider super.init() } func pasteboard( _ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType ) { // NSPasteboardItemDataProvider requires synchronous data return, but // NSItemProvider.loadDataRepresentation is async. We use a semaphore // to block until the async load completes. This is safe because AppKit // calls this method on a background thread during drag operations. let semaphore = DispatchSemaphore(value: 0) var result: Data? itemProvider.loadDataRepresentation(forTypeIdentifier: type.rawValue) { data, _ in result = data semaphore.signal() } // Wait for the data to load semaphore.wait() // Set it. I honestly don't know what happens here if this fails. if let data = result { item.setData(data, forType: type) } } }