下面给你一个 全面、可直接落地的「沙盒文件 → iCloud Document」移动/迁移操作清单,包括需要的 API、注意事项、元数据同步、进度处理等。确保你能在自己的文件管理 App 中正确实现。


✅ 一、涉及的目录

1. 沙盒目录(App 内部)

例如:

  • Documents/
  • Library/
  • tmp/

这些都只能本地访问。

2. iCloud Document(基于 UIDocument / NSFileManager)

通常是:

let ubiquityURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?
    .appendingPathComponent("Documents")

用户的 iCloud 会自动同步该目录。


✅ 二、移动文件的本质步骤

沙盒 → iCloud 本质是两个操作:

  1. 拷贝/移动本地文件到 Ubiquity Container
  2. 让 iCloud 接管该文件并同步

⚙️ 步骤 1:获取 iCloud Document 目录 URL

guard let containerURL = FileManager.default.url(
    forUbiquityContainerIdentifier: nil
)?.appendingPathComponent("Documents") else {
    print("iCloud 未启用")
    return
}

try FileManager.default.createDirectory(
    at: containerURL,
    withIntermediateDirectories: true,
    attributes: nil
)

⚙️ 步骤 2:执行文件移动/复制

🔹 推荐:copy → remove(比 move 更稳定)

Apple 官方不建议直接 move,因为 iCloud 目录是特殊文件系统。

let source = localURL        // 沙盒文件
let destination = containerURL.appendingPathComponent(source.lastPathComponent)

do {
    try FileManager.default.copyItem(at: source, to: destination)

    // 拷贝成功后删除本地
    try FileManager.default.removeItem(at: source)

} catch {
    print("移动失败:\(error)")
}

⚙️ 步骤 3:启动 iCloud 对文件的管理

对于 Ubiquity 文件,iCloud 会自动同步,但你可以手动触发:

var values = URLResourceValues()
values.isExcludedFromBackup = false
try? destination.setResourceValues(values)

⚙️ 步骤 4:监听 iCloud 同步状态(可选)

监听 NSMetadataQueryDidUpdateNSMetadataQueryDidFinishGathering

query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K like '*'", NSMetadataItemFSNameKey)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]

在回调里获取文件状态:

let status = item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String

可返回:

  • current
  • downloaded
  • notDownloaded

⚙️ 步骤 5:上传进度(iCloud 上传)

iCloud 对上传没有 progress API,但你可以:

✔ 监听 NSMetadataItemPercentUploadedKey

if let percent = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double {
    print("上传进度:\(percent)")
}

⚙️ 步骤 6:冲突处理

当用户另一台设备编辑了同一文件,你会收到:

  • NSFileVersion 冲突
let unresolved = NSFileVersion.unresolvedConflictVersionsOfItem(at: url) ?? []
for version in unresolved {
    version.isResolved = true
}

也可以让 UIDocument 自动合并。


👉 关键提醒(非常重要)

1. 不要使用 FileManager.moveItem 直接移动

因为沙盒 → iCloud 是不同文件系统,有时会失败或导致文件丢失。
正确策略:

✔ 复制
✔ 校验
✔ 删除原文件


2. 在 iCloud 目录中的文件自动同步,不需要你上传

唯一要做的事就是把文件放到 iCloud Document 文件夹里。


3. 大文件复制要用后台队列

避免阻塞主线程:

DispatchQueue.global().async {
    try? FileManager.default.copyItem(...)
}

4. 不要把整个目录移动到 iCloud

建议文件级迁移,不建议目录级 move。


📌 最佳实践的完整方法(可直接用)

func moveToICloud(from localURL: URL) throws -> URL? {
    guard let container = FileManager.default.url(
        forUbiquityContainerIdentifier: nil
    )?.appendingPathComponent("Documents") else {
        return nil
    }

    try FileManager.default.createDirectory(at: container, withIntermediateDirectories: true)

    let dest = container.appendingPathComponent(localURL.lastPathComponent)

    // copy
    try FileManager.default.copyItem(at: localURL, to: dest)

    // delete local
    try FileManager.default.removeItem(at: localURL)

    return dest
}

如果你愿意,我可以帮你:

✔ 设计你文件管理 App 的 iCloud 文件操作架构
✔ 提供下载、上传、同步、冲突、进度的完整代码
✔ 优化你的 NSMetadataQuery
✔ 做成可复用模块(Protocol + Manager)

你现在想了解哪个部分?