SwiftUI中如何通过NSItemProvider接收NSFilePromiseReceiver?
解决macOS SwiftUI列表接收Photos拖放全分辨率图片的文件承诺问题
问题背景
现有一个支持拖放图片的SwiftUI列表,能处理普通图片和文件URL,但无法接收来自Photos app的文件承诺(以此获取全分辨率图片)。已能检测到文件承诺类型,但处理时loadItem返回的是plist数据,无法匹配到NSFilePromiseReceiver。
关键问题分析
直接调用provider.loadItem(forTypeIdentifier:)获取文件承诺时,返回的是序列化的plist数据,而非期望的NSFilePromiseReceiver实例。正确的做法是通过loadObject(ofClass: NSFilePromiseReceiver.self)来加载,因为NSItemProvider需要明确指定要加载的类类型。
解决方案代码调整
1. 更新列表的拖放支持类型
在onInsert的类型参数中,加入文件承诺的支持类型:
List { ForEach(blockManager.blockViewModels, id: \.id) { viewModel in Image(nsImage: viewModel.imageBlock.image) .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: 1024) } .onInsert(of: [.image, .fileURL, .text] + NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) }) { index, itemProviders in for provider in itemProviders { // 优先处理文件承诺 if provider.canLoadObject(ofClass: NSFilePromiseReceiver.self) { _ = provider.loadObject(ofClass: NSFilePromiseReceiver.self) { [weak self] receiver, error in guard let self = self, let receiver = receiver else { return } self.handleFilePromise(receiver, dropIndex: index) } } // 处理普通图片/URL else if provider.canLoadObject(ofClass: NSImage.self) { _ = provider.loadObject(ofClass: URL.self) { url, error in guard let self = self, let url = url, let image = NSImage(contentsOf: url)?.resized(width: self.preferredWidth) else { return } let block = ImageBlock(image: image, filename: url.lastPathComponent) DispatchQueue.main.async { // 将图片插入数据模型 self.blockManager.blockViewModels.insert(/* 对应viewModel实例 */, at: index) } } } } } }
2. 正确实现文件承诺处理函数
private func handleFilePromise(_ receiver: NSFilePromiseReceiver, dropIndex: Int) { // 创建临时目录用于接收文件(确保目录存在) let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent("PhotoDropTemp", isDirectory: true) try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) receiver.receivePromisedFiles(atDestination: tempDir, options: [:], operationQueue: .global()) { [weak self] fileURL, error in guard let self = self else { return } if let error = error { print("文件承诺接收失败: \(error.localizedDescription)") return } guard let fileURL = fileURL else { return } // 读取图片并更新UI DispatchQueue.main.async { if let image = NSImage(contentsOf: fileURL)?.resized(width: self.preferredWidth) { let block = ImageBlock(image: image, filename: fileURL.lastPathComponent) self.blockManager.blockViewModels.insert(/* 对应viewModel实例 */, at: dropIndex) } // 可选:清理临时文件 try? FileManager.default.removeItem(at: fileURL) } } }
3. 简化类型检测逻辑
之前的itemPromiseType函数可以移除,直接通过canLoadObject(ofClass: NSFilePromiseReceiver.self)判断是否支持文件承诺,无需手动遍历类型。
核心修正点
- 加载方式:用
loadObject(ofClass: NSFilePromiseReceiver.self)替代loadItem(forTypeIdentifier:),直接获取NSFilePromiseReceiver实例,避免解析plist数据的额外操作。 - 路径处理:确保接收路径是有效存在的目录,
NSFilePromiseReceiver需要将承诺文件保存到指定目录下,而非单个文件路径。 - 线程切换:处理完文件后切回主线程更新UI和数据模型,避免跨线程操作问题。
内容的提问来源于stack exchange,提问作者Dejal
相关产品推荐
相关产品推荐

