You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 20:10:30