SwiftUI自定义NSItemProviderWriting拖至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 } }
问题根源
- URL数据格式错误:对
public.file-url类型使用NSKeyedArchiver归档URL,生成的bplist数据被系统识别为无效的绝对URL,而非原生URL表示。 - 文件存在性缺失:未确保目标文件实际存在,导致沙箱权限扩展无法生成。
- 强制解包风险:
try!可能引发崩溃,同时掩盖文件写入失败的问题。
修正方案
修改loadData方法中public.file-url类型的处理逻辑:
- 确保目标文件存在,若不存在则写入内容
- 使用URL原生的
dataRepresentation传递数据,无需归档 - 替换强制解包为安全的错误处理
修正后的自定义类代码
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

