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

使用UIDocumentPickerViewController访问iCloud时URL异常不可用

iOS UIDocumentPickerController 读取iCloud目录文件异常问题

我开发的iOS应用通过UIDocumentPickerController提供目录选择对话框,多数场景运行正常,但选择iCloud账户目录时,枚举得到的是带.xxx.icloud后缀的占位文件URL,复制到沙盒后内容并非原文件,而是URL格式的占位数据,无法正常使用。

相关代码

初始化UIDocumentPickerController

var documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open)
documentPicker.delegate = self
sView.present(documentPicker, animated: true, completion: nil) 

枚举目录文件

NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in
    let access = url.startAccessingSecurityScopedResource()
    var directoryContents: [URL]
    do {
        let keys : [URLResourceKey] = [.nameKey, .isDirectoryKey]
        directoryContents = try FileManager.default.contentsOfDirectory(
            at: url,
            includingPropertiesForKeys: keys)
    } catch {
        print("error \(error))")
        if access {
            url.stopAccessingSecurityScopedResource()
        }
        return
    }
    if access {
        url.stopAccessingSecurityScopedResource()
    }
}

文件复制到沙盒

for fromURL in directoryContents {
    var lastPath = fromURL.lastPathComponent
    print("Lastpath is \(lastPath)")
                
    let toURL: URL = documentURL.appendingPathComponent(lastPath)
                
    print("fromURL - \(fromURL)")
    print("toURL \(toURL)")
                
    let fM = FileManager.default
                
    if(fM.fileExists(atPath: toURL.path)) {
        exists += 1
    } else {
        do {
            let access = url.startAccessingSecurityScopedResource()
            try fM.copyItem(at: fromURL, to: toURL)
            if access {
                url.stopAccessingSecurityScopedResource()
            }
            count += 1
        } catch {
            print("Error on file copy \(error)")
        }
    }
}

异常现象

选择iCloud目录时,输出的URL带有前缀.和后缀.icloud:

fromURL - file:///Users/greg/Library/Developer/CoreSimulator/Devices/FFDED20B-B142-4FC6-BA8F-C1DC193E2AB7/data/Library/Mobile%20Documents/com~apple~CloudDocs/Savvy/SavvyLink/SmallEngineDataRepo/.Flt0002_20180913P.csv.icloud
toURL file:///Users/greg/Library/Developer/CoreSimulator/Devices/FFDED20B-B142-4FC6-BA8F-C1DC193E2AB7/data/Containers/Data/Application/167AC69D-494C-4CB1-8A36-E230C5303F30/Documents/.Flt0002_20180913P.csv.icloud

复制到沙盒的文件内容不是原文件,而是指向云端文件的URL格式数据。

解决方案

1. 识别iCloud占位文件并获取真实URL

.xxx.icloud是iCloud Drive的本地占位文件,仅存储元数据,需通过URLResourceKey解析真实文件URL:

NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (coordinatedURL) in
    let access = coordinatedURL.startAccessingSecurityScopedResource()
    defer { if access { coordinatedURL.stopAccessingSecurityScopedResource() } }
    
    var directoryContents: [URL]
    do {
        // 添加iCloud相关资源键
        let keys: [URLResourceKey] = [.nameKey, .isDirectoryKey, .isUbiquitousItemKey, .ubiquitousItemURLKey]
        directoryContents = try FileManager.default.contentsOfDirectory(
            at: coordinatedURL,
            includingPropertiesForKeys: keys)
        
        // 替换占位文件URL为真实iCloud文件URL
        directoryContents = directoryContents.compactMap { itemURL in
            var isUbiquitous: Bool = false
            try? itemURL.getResourceValue(&isUbiquitous, forKey: .isUbiquitousItemKey)
            
            if isUbiquitous {
                var cloudURL: URL?
                try? itemURL.getResourceValue(&cloudURL, forKey: .ubiquitousItemURLKey)
                return cloudURL ?? itemURL
            }
            return itemURL
        }
    } catch {
        print("error \(error))")
        return
    }
}

2. 确保iCloud文件下载完成后再复制

iCloud文件可能未缓存到本地,需触发下载并等待完成:

for fromURL in directoryContents {
    var lastPath = fromURL.lastPathComponent
    // 清理占位文件的特殊命名
    if lastPath.hasPrefix(".") && lastPath.hasSuffix(".icloud") {
        lastPath = String(lastPath.dropFirst().dropLast(7))
    }
    let toURL = documentURL.appendingPathComponent(lastPath)
    
    let fM = FileManager.default
    if fM.fileExists(atPath: toURL.path) {
        exists += 1
    } else {
        do {
            let access = fromURL.startAccessingSecurityScopedResource()
            defer { if access { fromURL.stopAccessingSecurityScopedResource() } }
            
            // 检查并触发iCloud文件下载
            var isUbiquitous: Bool = false
            try fromURL.getResourceValue(&isUbiquitous, forKey: .isUbiquitousItemKey)
            if isUbiquitous {
                try fM.startDownloadingUbiquitousItem(at: fromURL)
                // 等待下载完成(实际开发建议用异步回调优化)
                while fM.isUbiquitousItemDownloading(at: fromURL) {
                    Thread.sleep(forTimeInterval: 0.1)
                }
            }
            
            try fM.copyItem(at: fromURL, to: toURL)
            count += 1
        } catch {
            print("Error on file copy \(error)")
        }
    }
}

3. 修正安全作用域资源的使用

安全作用域资源需针对单个文件URL调用startAccessingSecurityScopedResource(),而非目录URL,确保每个文件的访问权限正确。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:25:25