axiom-icloud-drive-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiCloud Drive Reference
iCloud Drive 参考文档
Purpose: Comprehensive reference for file-based iCloud sync using ubiquitous containers
Availability: iOS 5.0+ (basic), iOS 8.0+ (iCloud Drive), iOS 11.0+ (modern APIs)
Context: File-based cloud storage, not database (use CloudKit for structured data)
用途:基于通用容器的、全面的基于文件的iCloud同步参考文档
适用版本:iOS 5.0+(基础功能),iOS 8.0+(iCloud Drive),iOS 11.0+(现代API)
适用场景:基于文件的云存储,而非数据库(结构化数据请使用CloudKit)
When to Use This Skill
何时使用本技能
Use this skill when:
- Implementing document-based iCloud sync
- Syncing user files across devices
- Building document-based apps (like Pages, Numbers)
- Coordinating file access across processes
- Handling iCloud file conflicts
- Using NSUbiquitousKeyValueStore for preferences
NOT for: Structured data with relationships (use instead)
axiom-cloudkit-ref在以下场景使用本技能:
- 实现基于文档的iCloud同步
- 在多设备间同步用户文件
- 构建基于文档的应用(如Pages、Numbers)
- 跨进程协调文件访问
- 处理iCloud文件冲突
- 使用NSUbiquitousKeyValueStore同步偏好设置
不适用场景:带关联关系的结构化数据(请使用替代)
axiom-cloudkit-refOverview
概述
iCloud Drive is for FILE-BASED sync, not structured data.
Use when:
- User creates/edits documents
- Files need to sync like Dropbox
- Document picker integration
Don't use when:
- Need queryable structured data (use CloudKit)
- Need relationships between records (use CloudKit)
- Small key-value preferences (use NSUbiquitousKeyValueStore)
iCloud Drive适用于基于文件的同步,而非结构化数据。
适用场景:
- 用户创建/编辑文档
- 文件需要像Dropbox一样同步
- 集成文档选择器
不适用场景:
- 需要可查询的结构化数据(请使用CloudKit)
- 需要记录间的关联关系(请使用CloudKit)
- 小型键值对偏好设置(请使用NSUbiquitousKeyValueStore)
Ubiquitous Containers
通用容器
Getting Ubiquitous Container URL
获取通用容器URL
swift
// ✅ CORRECT: Get iCloud container
func getICloudContainerURL() -> URL? {
// nil = use first container in entitlements
return FileManager.default.url(
forUbiquityContainerIdentifier: nil
)
}
// ✅ Check if iCloud is available
if let iCloudURL = getICloudContainerURL() {
print("iCloud available: \(iCloudURL)")
} else {
print("iCloud not available (not signed in or no entitlement)")
}swift
// ✅ CORRECT: Get iCloud container
func getICloudContainerURL() -> URL? {
// nil = use first container in entitlements
return FileManager.default.url(
forUbiquityContainerIdentifier: nil
)
}
// ✅ Check if iCloud is available
if let iCloudURL = getICloudContainerURL() {
print("iCloud available: \(iCloudURL)")
} else {
print("iCloud not available (not signed in or no entitlement)")
}Container Structure
容器结构
iCloud Container/
├── Documents/ # User-visible files (Files app)
│ └── MyApp/ # Your app's documents
├── Library/ # Hidden from user
│ ├── Application Support/
│ └── Caches/iCloud Container/
├── Documents/ # User-visible files (Files app)
│ └── MyApp/ # Your app's documents
├── Library/ # Hidden from user
│ ├── Application Support/
│ └── Caches/Saving to iCloud Drive
保存文件至iCloud Drive
swift
// ✅ CORRECT: Save document to iCloud
func saveToICloud(data: Data, filename: String) throws {
guard let iCloudURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) else {
throw iCloudError.notAvailable
}
let documentsURL = iCloudURL.appendingPathComponent("Documents")
// Create directory if needed
try FileManager.default.createDirectory(
at: documentsURL,
withIntermediateDirectories: true
)
let fileURL = documentsURL.appendingPathComponent(filename)
// Use file coordination for safe access
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(
writingItemAt: fileURL,
options: .forReplacing,
error: &error
) { newURL in
try? data.write(to: newURL)
}
if let error = error {
throw error
}
}swift
// ✅ CORRECT: Save document to iCloud
func saveToICloud(data: Data, filename: String) throws {
guard let iCloudURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) else {
throw iCloudError.notAvailable
}
let documentsURL = iCloudURL.appendingPathComponent("Documents")
// Create directory if needed
try FileManager.default.createDirectory(
at: documentsURL,
withIntermediateDirectories: true
)
let fileURL = documentsURL.appendingPathComponent(filename)
// Use file coordination for safe access
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(
writingItemAt: fileURL,
options: .forReplacing,
error: &error
) { newURL in
try? data.write(to: newURL)
}
if let error = error {
throw error
}
}File Coordination (Critical for Safety)
文件协调(安全关键)
Always use NSFileCoordinator when accessing iCloud files. This prevents:
- Race conditions with sync
- Data corruption
- Lost updates
访问iCloud文件时务必使用NSFileCoordinator,这可以避免:
- 同步时的竞争条件
- 数据损坏
- 更新丢失
Reading Files
读取文件
swift
// ✅ CORRECT: Coordinated read
func readICloudFile(url: URL) throws -> Data {
let coordinator = NSFileCoordinator()
var data: Data?
var coordinationError: NSError?
coordinator.coordinate(
readingItemAt: url,
options: [],
error: &coordinationError
) { newURL in
data = try? Data(contentsOf: newURL)
}
if let error = coordinationError {
throw error
}
guard let data = data else {
throw fileError.readFailed
}
return data
}swift
// ✅ CORRECT: Coordinated read
func readICloudFile(url: URL) throws -> Data {
let coordinator = NSFileCoordinator()
var data: Data?
var coordinationError: NSError?
coordinator.coordinate(
readingItemAt: url,
options: [],
error: &coordinationError
) { newURL in
data = try? Data(contentsOf: newURL)
}
if let error = coordinationError {
throw error
}
guard let data = data else {
throw fileError.readFailed
}
return data
}Writing Files
写入文件
swift
// ✅ CORRECT: Coordinated write
func writeICloudFile(data: Data, to url: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: url,
options: .forReplacing,
error: &coordinationError
) { newURL in
try? data.write(to: newURL)
}
if let error = coordinationError {
throw error
}
}swift
// ✅ CORRECT: Coordinated write
func writeICloudFile(data: Data, to url: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: url,
options: .forReplacing,
error: &coordinationError
) { newURL in
try? data.write(to: newURL)
}
if let error = coordinationError {
throw error
}
}Moving Files
移动文件
swift
// ✅ CORRECT: Coordinated move
func moveFile(from sourceURL: URL, to destURL: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: sourceURL,
options: .forMoving,
writingItemAt: destURL,
options: .forReplacing,
error: &coordinationError
) { newSource, newDest in
try? FileManager.default.moveItem(at: newSource, to: newDest)
}
if let error = coordinationError {
throw error
}
}swift
// ✅ CORRECT: Coordinated move
func moveFile(from sourceURL: URL, to destURL: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: sourceURL,
options: .forMoving,
writingItemAt: destURL,
options: .forReplacing,
error: &coordinationError
) { newSource, newDest in
try? FileManager.default.moveItem(at: newSource, to: newDest)
}
if let error = coordinationError {
throw error
}
}URL Resource Values for iCloud
iCloud相关的URL资源属性
Checking iCloud Status
检查iCloud状态
swift
// ✅ Check if file is in iCloud
func isInICloud(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.isUbiquitousItemKey])
return values?.isUbiquitousItem ?? false
}
// ✅ Check download status
func getDownloadStatus(url: URL) -> String {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingStatusKey,
.ubiquitousItemIsDownloadingKey,
.ubiquitousItemDownloadingErrorKey
])
if let downloading = values?.ubiquitousItemIsDownloading, downloading {
return "Downloading..."
}
if let status = values?.ubiquitousItemDownloadingStatus {
switch status {
case .current:
return "Downloaded"
case .notDownloaded:
return "Not downloaded (iCloud only)"
case .downloaded:
return "Downloaded"
@unknown default:
return "Unknown"
}
}
return "Unknown"
}
// ✅ Check upload status
func isUploading(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.ubiquitousItemIsUploadingKey])
return values?.ubiquitousItemIsUploading ?? false
}
// ✅ Check for conflicts
func hasConflicts(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemHasUnresolvedConflictsKey
])
return values?.ubiquitousItemHasUnresolvedConflicts ?? false
}swift
// ✅ Check if file is in iCloud
func isInICloud(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.isUbiquitousItemKey])
return values?.isUbiquitousItem ?? false
}
// ✅ Check download status
func getDownloadStatus(url: URL) -> String {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingStatusKey,
.ubiquitousItemIsDownloadingKey,
.ubiquitousItemDownloadingErrorKey
])
if let downloading = values?.ubiquitousItemIsDownloading, downloading {
return "Downloading..."
}
if let status = values?.ubiquitousItemDownloadingStatus {
switch status {
case .current:
return "Downloaded"
case .notDownloaded:
return "Not downloaded (iCloud only)"
case .downloaded:
return "Downloaded"
@unknown default:
return "Unknown"
}
}
return "Unknown"
}
// ✅ Check upload status
func isUploading(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.ubiquitousItemIsUploadingKey])
return values?.ubiquitousItemIsUploading ?? false
}
// ✅ Check for conflicts
func hasConflicts(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemHasUnresolvedConflictsKey
])
return values?.ubiquitousItemHasUnresolvedConflicts ?? false
}Downloading Files
下载文件
swift
// ✅ CORRECT: Request download
func downloadFromICloud(url: URL) throws {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
}
// ✅ Monitor download progress
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K == %@",
NSMetadataItemURLKey, url as NSURL)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { notification in
// Check progress
if let item = query.results.first as? NSMetadataItem {
if let percent = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double {
print("Downloaded: \(percent)%")
}
}
}
query.start()swift
// ✅ CORRECT: Request download
func downloadFromICloud(url: URL) throws {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
}
// ✅ Monitor download progress
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K == %@",
NSMetadataItemURLKey, url as NSURL)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { notification in
// Check progress
if let item = query.results.first as? NSMetadataItem {
if let percent = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double {
print("Downloaded: \(percent)%")
}
}
}
query.start()Conflict Resolution
冲突解决
Detecting Conflicts
检测冲突
swift
// ✅ Get conflict versions
func getConflictVersions(for url: URL) -> [NSFileVersion]? {
return NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
}swift
// ✅ Get conflict versions
func getConflictVersions(for url: URL) -> [NSFileVersion]? {
return NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
}Resolving Conflicts
解决冲突
swift
// ✅ CORRECT: Resolve conflicts
func resolveConflicts(at url: URL, keepingVersion: ConflictResolution) throws {
guard let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url),
!conflicts.isEmpty else {
return // No conflicts
}
let current = try NSFileVersion.currentVersionOfItem(at: url)
switch keepingVersion {
case .current:
// Keep current version, discard others
for conflict in conflicts {
conflict.isResolved = true
}
case .other(let chosenVersion):
// Replace current with chosen conflict version
try chosenVersion.replaceItem(at: url, options: [])
chosenVersion.isResolved = true
// Mark other conflicts as resolved
for conflict in conflicts where conflict != chosenVersion {
conflict.isResolved = true
}
case .manual:
// App merges manually, then marks resolved
let mergedData = mergeConflicts(current: current, conflicts: conflicts)
try mergedData.write(to: url)
for conflict in conflicts {
conflict.isResolved = true
}
}
// Remove resolved versions
try NSFileVersion.removeOtherVersionsOfItem(at: url)
}
enum ConflictResolution {
case current
case other(NSFileVersion)
case manual
}swift
// ✅ CORRECT: Resolve conflicts
func resolveConflicts(at url: URL, keepingVersion: ConflictResolution) throws {
guard let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url),
!conflicts.isEmpty else {
return // No conflicts
}
let current = try NSFileVersion.currentVersionOfItem(at: url)
switch keepingVersion {
case .current:
// Keep current version, discard others
for conflict in conflicts {
conflict.isResolved = true
}
case .other(let chosenVersion):
// Replace current with chosen conflict version
try chosenVersion.replaceItem(at: url, options: [])
chosenVersion.isResolved = true
// Mark other conflicts as resolved
for conflict in conflicts where conflict != chosenVersion {
conflict.isResolved = true
}
case .manual:
// App merges manually, then marks resolved
let mergedData = mergeConflicts(current: current, conflicts: conflicts)
try mergedData.write(to: url)
for conflict in conflicts {
conflict.isResolved = true
}
}
// Remove resolved versions
try NSFileVersion.removeOtherVersionsOfItem(at: url)
}
enum ConflictResolution {
case current
case other(NSFileVersion)
case manual
}NSUbiquitousKeyValueStore (Preferences Sync)
NSUbiquitousKeyValueStore(偏好设置同步)
For small preferences only (<1 MB total, <1024 keys)
swift
// ✅ CORRECT: Sync small preferences
let store = NSUbiquitousKeyValueStore.default
// Set values
store.set(true, forKey: "darkModeEnabled")
store.set(2.0, forKey: "textSizeMultiplier")
store.set(["en", "es"], forKey: "selectedLanguages")
// Synchronize
store.synchronize()
// Read values
let darkMode = store.bool(forKey: "darkModeEnabled")
let textSize = store.double(forKey: "textSizeMultiplier")
// Listen for changes from other devices
NotificationCenter.default.addObserver(
forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: store,
queue: .main
) { notification in
// Update UI with new values
updatePreferences()
}Limitations:
- Total storage: 1 MB
- Max keys: 1024
- Max value size: 1 MB
- Use only for preferences, not data
仅适用于小型偏好设置(总容量<1 MB,键数量<1024)
swift
// ✅ CORRECT: Sync small preferences
let store = NSUbiquitousKeyValueStore.default
// Set values
store.set(true, forKey: "darkModeEnabled")
store.set(2.0, forKey: "textSizeMultiplier")
store.set(["en", "es"], forKey: "selectedLanguages")
// Synchronize
store.synchronize()
// Read values
let darkMode = store.bool(forKey: "darkModeEnabled")
let textSize = store.double(forKey: "textSizeMultiplier")
// Listen for changes from other devices
NotificationCenter.default.addObserver(
forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: store,
queue: .main
) { notification in
// Update UI with new values
updatePreferences()
}限制:
- 总存储容量:1 MB
- 最大键数量:1024
- 单值最大容量:1 MB
- 仅用于偏好设置,而非数据存储
Entitlements
权限配置
xml
<!-- iCloud capability -->
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<!-- Ubiquitous containers -->
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.com.example.app</string>
</array>
<!-- Key-value store (if using) -->
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.example.app</string>xml
<!-- iCloud capability -->
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<!-- Ubiquitous containers -->
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.com.example.app</string>
</array>
<!-- Key-value store (if using) -->
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.example.app</string>Common Patterns
常见模式
Pattern 1: Document Picker Integration
模式1:集成文档选择器
swift
// ✅ Present iCloud document picker
import UniformTypeIdentifiers
let picker = UIDocumentPickerViewController(
forOpeningContentTypes: [.pdf, .plainText]
)
picker.delegate = self
picker.allowsMultipleSelection = false
// Enable iCloud
picker.directoryURL = getICloudContainerURL()
present(picker, animated: true)swift
// ✅ Present iCloud document picker
import UniformTypeIdentifiers
let picker = UIDocumentPickerViewController(
forOpeningContentTypes: [.pdf, .plainText]
)
picker.delegate = self
picker.allowsMultipleSelection = false
// Enable iCloud
picker.directoryURL = getICloudContainerURL()
present(picker, animated: true)Pattern 2: Monitor Directory for Changes
模式2:监听目录变化
swift
// ✅ Monitor iCloud directory
class ICloudMonitor {
let query = NSMetadataQuery()
func startMonitoring(directory: URL) {
query.predicate = NSPredicate(format: "%K BEGINSWITH %@",
NSMetadataItemPathKey, directory.path)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { [weak self] _ in
self?.processResults()
}
query.start()
}
func processResults() {
for item in query.results {
if let metadataItem = item as? NSMetadataItem,
let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL {
print("File: \(url.lastPathComponent)")
}
}
}
}swift
// ✅ Monitor iCloud directory
class ICloudMonitor {
let query = NSMetadataQuery()
func startMonitoring(directory: URL) {
query.predicate = NSPredicate(format: "%K BEGINSWITH %@",
NSMetadataItemPathKey, directory.path)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { [weak self] _ in
self?.processResults()
}
query.start()
}
func processResults() {
for item in query.results {
if let metadataItem = item as? NSMetadataItem,
let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL {
print("File: \(url.lastPathComponent)")
}
}
}
}Quick Reference
速查参考
| Task | API | Notes |
|---|---|---|
| Get iCloud URL | | Returns nil if unavailable |
| Check if in iCloud | | Bool |
| Download file | | Async, monitor with NSMetadataQuery |
| Check download status | | current/notDownloaded/downloaded |
| Check for conflicts | | Bool |
| Resolve conflicts | | Manual merge or choose version |
| Sync preferences | | <1 MB total |
| File coordination | | Always use for iCloud files |
| 任务 | API | 说明 |
|---|---|---|
| 获取iCloud URL | | 不可用时返回nil |
| 检查文件是否在iCloud中 | | 返回布尔值 |
| 下载文件 | | 异步操作,通过NSMetadataQuery监听进度 |
| 检查下载状态 | | 状态包括current/notDownloaded/downloaded |
| 检查冲突 | | 返回布尔值 |
| 解决冲突 | | 手动合并或选择保留版本 |
| 同步偏好设置 | | 总容量<1 MB |
| 文件协调 | | 访问iCloud文件时务必使用 |
Related Skills
相关技能
- — Choose iCloud Drive vs CloudKit
axiom-storage - — For structured data sync
axiom-cloudkit-ref - — Debug iCloud sync issues
axiom-cloud-sync-diag
Last Updated: 2025-12-12
Skill Type: Reference
Minimum iOS: 5.0 (basic), 8.0 (iCloud Drive), 11.0 (modern APIs)
- — 选择iCloud Drive还是CloudKit
axiom-storage - — 用于结构化数据同步
axiom-cloudkit-ref - — 调试iCloud同步问题
axiom-cloud-sync-diag
最后更新:2025-12-12
技能类型:参考文档
最低iOS版本:5.0(基础功能),8.0(iCloud Drive),11.0(现代API)