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

如何确保UIDocumentPickerController选中文件夹内文件完成下载再压缩?

这个问题绝对是iCloud Drive文件处理里的经典坑——那些.icloud占位符文件看起来像正常文件,但实际只是指向云端的指针,直接压缩的话只会得到空的zip包。我来分享一套经过验证的方案,结合NSFileCoordinator和iCloud的下载API,确保所有文件完全落地后再执行压缩:

核心思路

必须先检查每个iCloud文件的下载状态,对未下载的文件触发云端下载,等待所有文件完成后再启动压缩流程。全程要通过NSFileCoordinator进行操作,避免iCloud文件的并发访问冲突。

1. 单个文件的下载检查与触发

拿到UIDocumentPickerController返回的URL后,先通过文件协调器获取安全的访问URL,再判断文件的iCloud状态:

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    guard let targetURL = urls.first else { return }
    
    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
    var coordinatorError: NSError?
    
    // 协调文件访问,确保操作安全
    fileCoordinator.coordinate(readingItemAt: targetURL, options: .withoutChanges, error: &coordinatorError) { coordinatedURL in
        let fileManager = FileManager.default
        
        // 跳过非iCloud文件
        guard fileManager.isUbiquitousItem(at: coordinatedURL) else {
            self.compressFile(at: coordinatedURL)
            return
        }
        
        do {
            // 获取文件的下载状态
            let downloadStatus = try fileManager.ubiquitousItemDownloadingStatus(for: coordinatedURL)
            
            switch downloadStatus {
            case .current, .downloaded:
                // 文件已在本地,直接压缩
                self.compressFile(at: coordinatedURL)
            case .notDownloaded:
                // 触发云端下载,等待完成后再压缩
                fileManager.startDownloadingUbiquitousItem(at: coordinatedURL) { downloadError in
                    if let downloadError = downloadError {
                        print("下载失败:\(downloadError.localizedDescription)")
                        // 这里可以给用户弹出错误提示
                        return
                    }
                    self.compressFile(at: coordinatedURL)
                }
            @unknown default:
                fatalError("遇到未知的iCloud文件下载状态")
            }
        } catch {
            print("检查文件状态失败:\(error.localizedDescription)")
        }
    }
    
    if let error = coordinatorError {
        print("文件协调失败:\(error.localizedDescription)")
    }
}

2. 文件夹层级的递归处理

如果用户选中的是文件夹,需要递归遍历所有子文件/子文件夹,确保每一个iCloud文件都完成下载:

// 递归下载文件夹内所有内容
func downloadAllItems(in folderURL: URL, completion: @escaping (Error?) -> Void) {
    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
    let dispatchGroup = DispatchGroup()
    var encounteredError: Error?
    
    fileCoordinator.coordinate(readingItemAt: folderURL, options: .withoutChanges) { coordinatedFolderURL in
        do {
            // 获取文件夹内所有内容,同时请求iCloud相关属性
            let contents = try FileManager.default.contentsOfDirectory(
                at: coordinatedFolderURL,
                includingPropertiesForKeys: [.isUbiquitousItemKey, .ubiquitousItemDownloadingStatusKey],
                options: .skipsHiddenFiles
            )
            
            for itemURL in contents {
                dispatchGroup.enter()
                
                // 判断是否为文件夹,递归处理
                var isDirectory: ObjCBool = false
                if FileManager.default.fileExists(atPath: itemURL.path, isDirectory: &isDirectory), isDirectory.boolValue {
                    self.downloadAllItems(in: itemURL) { error in
                        encounteredError = error ?? encounteredError
                        dispatchGroup.leave()
                    }
                } else {
                    // 处理单个文件
                    self.downloadSingleItem(at: itemURL) { error in
                        encounteredError = error ?? encounteredError
                        dispatchGroup.leave()
                    }
                }
            }
        } catch {
            encounteredError = error
            // 手动触发leave,避免group永远等待
            dispatchGroup.leave()
        }
    }
    
    // 等待所有下载任务完成
    dispatchGroup.notify(queue: .main) {
        completion(encounteredError)
    }
}

// 单个文件的下载逻辑封装
func downloadSingleItem(at url: URL, completion: @escaping (Error?) -> Void) {
    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
    
    fileCoordinator.coordinate(readingItemAt: url, options: .withoutChanges) { coordinatedURL in
        let fileManager = FileManager.default
        
        guard fileManager.isUbiquitousItem(at: coordinatedURL) else {
            completion(nil)
            return
        }
        
        do {
            let downloadStatus = try fileManager.ubiquitousItemDownloadingStatus(for: coordinatedURL)
            switch downloadStatus {
            case .current, .downloaded:
                completion(nil)
            case .notDownloaded:
                fileManager.startDownloadingUbiquitousItem(at: coordinatedURL, completionHandler: completion)
            @unknown default:
                completion(NSError(domain: "YourAppDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "未知的下载状态"]))
            }
        } catch {
            completion(error)
        }
    }
}

然后在documentPicker中调用这个递归方法,等全部下载完成后再压缩:

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    guard let targetURL = urls.first else { return }
    
    var isDirectory: ObjCBool = false
    if FileManager.default.fileExists(atPath: targetURL.path, isDirectory: &isDirectory), isDirectory.boolValue {
        // 处理文件夹
        downloadAllItems(in: targetURL) { error in
            if let error = error {
                print("文件夹内容下载失败:\(error.localizedDescription)")
                return
            }
            self.compressFolder(at: targetURL)
        }
    } else {
        // 处理单个文件
        downloadSingleItem(at: targetURL) { error in
            if let error = error {
                print("文件下载失败:\(error.localizedDescription)")
                return
            }
            self.compressFile(at: targetURL)
        }
    }
}

3. 关键注意事项

  • 用户体验:大文件夹下载可能耗时很久,一定要给用户显示进度提示(比如用Progress类配合UIProgressView),避免用户误以为APP卡顿。
  • 取消逻辑:如果用户中途取消操作,记得调用FileManager.default.cancelDownloadingUbiquitousItem(at:)终止下载,节省带宽和存储空间。
  • 空间检查:下载前最好检查本地剩余存储空间,避免因空间不足导致下载失败。
  • 后台支持:如果需要在后台继续下载,要在Info.plist中添加UIBackgroundModes的fetch和remote-notification权限,同时配置iCloud的后台下载策略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:22:38