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

SwiftUI自定义NSItemProviderWriting拖至Finder遇URL及权限错误

解决SwiftUI拖拽到Finder的沙箱权限与URL无效问题

问题场景

正在构建带可拖拽内容的SwiftUI视图,通过自定义NSItemProviderWriting类实现:

  • 应用内拖拽时提供自定义类型数据
  • 拖至Finder时提供文件URL类型数据

应用内拖拽正常,但拖至Finder时控制台出现以下错误:

NSURLs written to the pasteboard via NSPasteboardWriting must be absolute URLs. NSURL 'bplist00%C3%94%01%02%03%04%05%06%07%0AX$versionY$archiverT$topX$objects%12%00%01%C2%86%C2%A0_%10%0FNSKeyedArchiver%C3%91 ... 00%01%1B' is not an absolute URL.
[sandbox] CreateSandboxExtensionData failed: urlData: 0x600000be9640 length: 0 (-1 means null)
[general] Sandbox extension data required immediately for flavor public.file-url, but failed to obtain.
[sandbox] CreateSandboxExtensionData failed: urlData: 0x600000be9640 length: 0 (-1 means null)
[sandbox] Failed to get a sandbox extension

相关代码

视图部分

myView
    .onDrag {
        NSItemProvider(object: TestDragProvider("dragged text"))
    }

自定义NSItemProviderWriting类

class TestDragProvider: NSObject, NSItemProviderWriting {
    var stringValue: String

    init(_ value: String) {
        self.stringValue = value
    }

    static var writableTypeIdentifiersForItemProvider: [String] {
        let utTypes: [UTType] = [
            myCustomUTType,
            .fileUrl
        ]
        return utTypes.map { $0.identifier }
    }

    func loadData(
        withTypeIdentifier typeIdentifier: String,
        forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void
    ) -> Progress? {
        let progress = Progress(totalUnitCount: 1)
        var data: Data? = nil
        
        print("Loading data for type: \(typeIdentifier)")
        if let utType = UTType(typeIdentifier) {
            if utType == myCustomUTType {
                data = stringValue.data(using: .utf8)
            } else if utType == .fileUrl {
                let urlOfSomeFile = FileManager
                                        .default
                                        .urls(for: .cachesDirectory, in: .userDomainMask)[0]
                                        .appendingPathComponent("fileToDrag.txt")
                                        .absoluteURL
                data = try! NSKeyedArchiver.archivedData(withRootObject: (urlOfSomeFile as NSURL), requiringSecureCoding: false)
            }
        }

        progress.completedUnitCount = data != nil ? 1 : 0
        completionHandler(data, nil)
        return progress
    }
}

问题根源

  1. URL数据格式错误:对public.file-url类型使用NSKeyedArchiver归档URL,生成的bplist数据被系统识别为无效的绝对URL,而非原生URL表示。
  2. 文件存在性缺失:未确保目标文件实际存在,导致沙箱权限扩展无法生成。
  3. 强制解包风险:try!可能引发崩溃,同时掩盖文件写入失败的问题。

修正方案

修改loadData方法中public.file-url类型的处理逻辑:

  1. 确保目标文件存在,若不存在则写入内容
  2. 使用URL原生的dataRepresentation传递数据,无需归档
  3. 替换强制解包为安全的错误处理

修正后的自定义类代码

class TestDragProvider: NSObject, NSItemProviderWriting {
    var stringValue: String

    init(_ value: String) {
        self.stringValue = value
    }

    static var writableTypeIdentifiersForItemProvider: [String] {
        let utTypes: [UTType] = [
            myCustomUTType,
            .fileUrl
        ]
        return utTypes.map { $0.identifier }
    }

    func loadData(
        withTypeIdentifier typeIdentifier: String,
        forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void
    ) -> Progress? {
        let progress = Progress(totalUnitCount: 1)
        var data: Data? = nil
        var error: Error? = nil
        
        print("Loading data for type: \(typeIdentifier)")
        if let utType = UTType(typeIdentifier) {
            if utType == myCustomUTType {
                data = stringValue.data(using: .utf8)
            } else if utType == .fileUrl {
                let urlOfSomeFile = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
                    .appendingPathComponent("fileToDrag.txt")
                
                // 确保文件存在,写入拖拽内容
                if !FileManager.default.fileExists(atPath: urlOfSomeFile.path) {
                    do {
                        try stringValue.write(to: urlOfSomeFile, atomically: true, encoding: .utf8)
                    } catch let writeError {
                        error = writeError
                    }
                }
                
                // 使用URL原生的二进制表示,无需归档
                if error == nil {
                    data = urlOfSomeFile.dataRepresentation
                }
            }
        }

        progress.completedUnitCount = data != nil ? 1 : 0
        completionHandler(data, error)
        return progress
    }
}

关键说明

  • dataRepresentation是URL的标准二进制格式,系统能直接识别为合法绝对URL,避免归档导致的格式错误。
  • 必须确保文件实际存在:沙箱扩展基于真实文件生成,空URL或不存在的文件会触发权限获取失败。
  • 错误处理:替换try!为do-catch,能及时捕获文件写入失败的问题,便于调试。

内容的提问来源于stack exchange,提问作者RL2000

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:45:24