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

SwiftUI拖放功能在Mac上无法处理所有文件的public.data问题

拖放文件功能失效问题排查与修复

我正在开发SwiftUI应用,需要支持任意文件拖放并显示MIME类型,同时用NSOpenPanel做备选文件选择(该功能运行正常),但拖放功能处理所有文件均失败。

问题代码(HandleDrop)

private func handleDrop(providers: [NSItemProvider]) -> Bool {
    var success = false

    for provider in providers {
        print("Checking provider: \(provider)")

        // Handle public.file-url
        if provider.hasItemConformingToTypeIdentifier("public.file-url") {
            print("Provider conforms to public.file-url")
            provider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { item, error in
                if let url = item as? URL {
                    print("Dropped file URL: \(url)")
                    DispatchQueue.main.async {
                        droppedFileType = "MIME Type: \(url.mimeType ?? "Unknown")"
                    }
                    success = true
                } else {
                    print("Failed to cast item to URL for public.file-url")
                }
            }
        }

        // Handle public.data
        else if provider.hasItemConformingToTypeIdentifier("public.data") {
            print("Provider conforms to public.data")
            provider.loadItem(forTypeIdentifier: "public.data", options: nil) { item, error in
                if let data = item as? Data {
                    let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("pdf")
                    do {
                        try data.write(to: tempURL)
                        print("Saved file to temporary URL: \(tempURL)")
                        DispatchQueue.main.async {
                            droppedFileType = "MIME Type: \(tempURL.mimeType ?? "Unknown")"
                        }
                        success = true
                    } catch {
                        print("Failed to write data to temporary file")
                    }
                } else {
                    print("Failed to decode item as public.data")
                }
            }
        }

        // Unsupported type
        else {
            print("Provider does NOT conform to public.file-url or public.data")
        }
    }

    return success
}

现象说明

NSOpenPanel对所有文件类型都能正常工作,可正确检测并显示MIME类型。但拖放功能的provider虽符合public.data,却无法解码为Data,日志显示“Failed to decode item as public.data”,测试.png、.txt等文件均失败。

当前权限配置(.entitlement)

<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>

修复方案

1. 修正public.data的解码逻辑

public.data类型的item不一定直接是Swift的Data,可能是NSData或NSSecureCoding兼容对象,需要补充类型转换:
修改public.data分支的代码:

else if provider.hasItemConformingToTypeIdentifier("public.data") {
    print("Provider conforms to public.data")
    provider.loadItem(forTypeIdentifier: "public.data", options: nil) { item, error in
        guard let dataObj = item as? NSSecureCoding else {
            print("Failed to decode item as public.data")
            return
        }
        do {
            let data = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSData.self, from: dataObj as! Data) ?? NSData()
            let swiftData = Data(referencing: data)
            
            // 获取原文件扩展名(从provider的UTI转换)
            let ext = provider.registeredTypeIdentifiers.compactMap { UTType($0)?.preferredFilenameExtension }.first ?? "tmp"
            let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension(ext)
            
            try swiftData.write(to: tempURL)
            print("Saved file to temporary URL: \(tempURL)")
            DispatchQueue.main.async {
                droppedFileType = "MIME Type: \(tempURL.mimeType ?? "Unknown")"
            }
            success = true
        } catch {
            print("Failed to process public.data: \(error)")
        }
    }
}

2. 补充沙箱权限

拖放文件可能来自沙箱未授权的位置,需要添加书签权限以安全访问:
在.entitlement中添加:

<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>

同时,处理public.file-url时需要创建安全书签才能访问文件:

if provider.hasItemConformingToTypeIdentifier("public.file-url") {
    print("Provider conforms to public.file-url")
    provider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { item, error in
        if let url = item as? URL {
            do {
                let bookmark = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
                guard let securedURL = URL(resolvingBookmarkData: bookmark, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: nil) else {
                    print("Failed to resolve secured URL")
                    return
                }
                securedURL.startAccessingSecurityScopedResource()
                defer { securedURL.stopAccessingSecurityScopedResource() }
                
                print("Dropped file URL (secured): \(securedURL)")
                DispatchQueue.main.async {
                    droppedFileType = "MIME Type: \(securedURL.mimeType ?? "Unknown")"
                }
                success = true
            } catch {
                print("Failed to handle secure URL: \(error)")
            }
        } else {
            print("Failed to cast item to URL for public.file-url")
        }
    }
}

3. 修复临时文件扩展名硬编码问题

原代码中临时文件固定为.pdf,会导致非PDF文件的MIME类型检测错误,改为从provider的UTI获取对应扩展名。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:28:15