mcpField
A
axiom-icloud-drive-ref

axiom-icloud-drive-ref

github.com/charleswiltgen開発1.2k170 回インストール今日更新
リポジトリ

axiom-icloud-drive-ref について

iOS開発者向けのiCloud Driveファイル同期リファレンススキルです。ファイル調整、ダウンロード状況、競合処理を扱い、CloudKitやキー値ストアとの使い分けも説明します。

主な機能

  1. 01iCloudコンテナ内のファイル管理
  2. 02NSFileCoordinatorによる読み書きの調整
  3. 03ファイル状態の確認とダウンロード要求
  4. 04ファイルバージョン競合の検出と解決
  5. 05小規模な設定値の同期

活用シーン

  1. 01ドキュメントアプリのデバイス間同期
  2. 02iCloudファイルの安全な読み書き
  3. 03同期競合とダウンロード状況の処理
  4. 04少量のユーザー設定の同期

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-icloud-drive-ref

インストール後、Claude Code で/axiom-icloud-drive-refを入力して読み込まれたか確認します。

詳細

iCloud Drive リファレンス

目的: ユビキタスコンテナを使用したファイルベースの iCloud 同期に関する包括的なリファレンス 利用可能バージョン: iOS 5.0+(基本機能)、iOS 8.0+(iCloud Drive)、iOS 11.0+(最新 API) コンテキスト: ファイルベースのクラウドストレージであり、データベースではありません(構造化データには CloudKit を使用してください)

このスキルを使用する場面

次の場合にこのスキルを使用してください:

  • ドキュメントベースの iCloud 同期を実装する
  • デバイス間でユーザーファイルを同期する
  • ドキュメントベースのアプリ(Pages、Numbers など)を構築する
  • プロセス間のファイルアクセスを調整する
  • iCloud ファイルの競合に対処する
  • 環境設定に NSUbiquitousKeyValueStore を使用する

対象外:リレーションシップを持つ構造化データ(代わりに axiom-cloudkit-ref を使用してください)


概要

iCloud Drive はファイルベースの同期用であり、構造化データ用ではありません。

次の場合に使用します:

  • ユーザーがドキュメントを作成・編集する
  • Dropbox のようにファイルを同期する必要がある
  • ドキュメントピッカーとの統合

次の場合は使用しないでください:

  • クエリ可能な構造化データが必要(CloudKit を使用してください)
  • レコード間のリレーションシップが必要(CloudKit を使用してください)
  • 小規模なキーと値の環境設定(NSUbiquitousKeyValueStore を使用してください)

ユビキタスコンテナ

ユビキタスコンテナの URL を取得する
// ✅ 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)")
}
コンテナ構造
iCloud Container/
├── Documents/          # User-visible files (Files app)
│   └── MyApp/         # Your app's documents
├── Library/           # Hidden from user
│   ├── Application Support/
│   └── Caches/
iCloud Drive に保存する
// ✅ 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
    }
}

ファイル調整(安全性に不可欠)

iCloud ファイルにアクセスするときは、必ず NSFileCoordinator を使用してください。これにより、次の問題を防止できます:

  • 同期に伴う競合状態
  • データの破損
  • 更新の消失
ファイルの読み取り
// ✅ 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
}
ファイルの書き込み
// ✅ 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
    }
}
ファイルの移動
// ✅ 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
    }
}

iCloud の URL リソース値

iCloud の状態を確認する
// ✅ 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
}
ファイルをダウンロードする
// ✅ 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()

競合の解決

競合を検出する
// ✅ Get conflict versions
func getConflictVersions(for url: URL) -> [NSFileVersion]? {
    return NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
}
競合を解決する
// ✅ 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(環境設定の同期)

小規模な環境設定専用です(合計 <1 MB、<1024 個のキー)

// ✅ 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
  • 環境設定にのみ使用し、データには使用しないでください

エンタイトルメント


<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudDocuments</string>
</array>


<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
    <string>iCloud.com.example.app</string>
</array>


<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.example.app</string>

よくあるパターン

パターン 1:ドキュメントピッカーとの統合
// ✅ 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)
パターン 2:ディレクトリの変更を監視する
// ✅ 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)")
            }
        }
    }
}

クイックリファレンス

タスクAPI備考
iCloud URL を取得FileManager.default.url(forUbiquityContainerIdentifier:)利用できない場合は nil を返します
iCloud 内にあるか確認.isUbiquitousItemKey リソース値Bool
ファイルをダウンロードstartDownloadingUbiquitousItem(at:)非同期。NSMetadataQuery で監視します
ダウンロード状態を確認.ubiquitousItemDownloadingStatusKeycurrent/notDownloaded/downloaded
競合を確認.ubiquitousItemHasUnresolvedConflictsKeyBool
競合を解決NSFileVersion.unresolvedConflictVersionsOfItem(at:)手動でマージするか、バージョンを選択します
環境設定を同期NSUbiquitousKeyValueStore.default合計 <1 MB
ファイル調整NSFileCoordinatoriCloud ファイルでは必ず使用してください

関連スキル

  • axiom-storage — iCloud Drive と CloudKit のどちらを選ぶか
  • axiom-cloudkit-ref — 構造化データの同期用
  • axiom-cloud-sync-diag — iCloud 同期の問題をデバッグする

最終更新日: 2025-12-12 スキルの種類: リファレンス 最低 iOS バージョン: 5.0(基本機能)、8.0(iCloud Drive)、11.0(最新 API)

コメント