[Swift] iCloud DriveとローカルのDocumentsフォルダ間でファイルを移動する

スポンサーリンク

端末ごとのアプリ内設定に追加したiCloud同期機能をオン/オフ操作したタイミングで、ローカルとiCloud Drive、それぞれにあるDocumentsフォルダ内のファイルを移動させたくてコードを書いたので、備忘として記録します。

Swift勉強中なので、もっとこうしたら良いよ!ここ間違ってるよ!などあれば教えていただけると嬉しいです。

スポンサーリンク

開発環境

Xcode 12.0

スポンサーリンク

ローカル→iCloud DriveのDocumentsにファイルを移動する

移動後に、ローカルのファイルを削除して良い場合は、FileManagersetUbiquitousを使います。
ローカルのファイルを残しておきたい場合は、FileManagercopyItemを使います。

Apple Developer Documentation – setUbiquitous

setUbiquitous(_:itemAt:destinationURL:) | Apple Developer Documentation
Indicates whether the item at the specified URL should be stored in iCloud.

Apple Developer Documentation – copyItem

copyItem(atPath:toPath:) | Apple Developer Documentation
Copies the item at the specified path to a new location synchronously.

Apple Developer Documentationに、移動元と先でDirectory指定ができると書いてあったので、Directory丸ごとsetUbiquitousまたはcopyItemしようと思っていたのですが、思うようにいかなかったのでファイルを一つひとつ、移動元先のフルパスで指定しています。

// Documents Directory URL
let fm = FileManager.default
let localUrl = fm.urls(for: .documentDirectory, in: .userDomainMask)[0] // Local Document Directory
let iCloudUrl = fm.url(forUbiquityContainerIdentifier: iCloudContainerId)!.appendingPathComponent("Documents") // iCloud Document Directory
// ローカル->iCloud ファイル移動
func moveFilesToiCloud() {
    DispatchQueue.main.async {
        let fromUrl = localUrl
        let toUrl = iCloudUrl
        let enumerator = fm.enumerator(atPath: fromUrl.path)
        while let file = enumerator?.nextObject() as? String {
            do {
                let fromFileUrl = fromUrl.appendingPathComponent(file)
                let toFileUrl = toUrl.appendingPathComponent(file)
                //try fm.copyItem(atPath: fromFileUrl.path, toPath: toFileUrl.path) // ローカルのデータ残したままiCloudにアップロードする
                try fm.setUbiquitous(true, itemAt: fromFileUrl, destinationURL: toFileUrl) // iCloudへ転送し、ローカルのデータは削除する
                print("ローカル->iCloud:ファイル転送に成功しました")
            } catch {
                print("ローカル->iCloud:ファイル転送に失敗しました <File: \(file)> <Error: \(error.localizedDescription)>")
            }
        }
    }
}
スポンサーリンク

iCloud Drive→ローカルのDocumentsにファイルを移動する

iCloudサーバ上にファイルの実体があり、ローカルのiCloud Driveにはまだダウンロードされていない状態の場合、本来のファイル名の先頭に”.”、末尾に”.icloud”という文字が付与されていて、単純にcopyItemではローカルのDocumentsフォルダにコピーできません。

調べたところ、iCloudサーバからローカルの特定フォルダへファイルを移動する場合は、FileManagersetUbiquitousを使うのが正解らしいです。実際、まだローカルのiCloud Drive上にダウンロードされていないファイルでも、簡単に移動できました。

Apple Developer Documentation – setUbiquitous

setUbiquitous(_:itemAt:destinationURL:) | Apple Developer Documentation
Indicates whether the item at the specified URL should be stored in iCloud.

一つ問題があって、setUbiquitousが、ファイルを移動した後iCloud上のファイルを自動的に削除してしまう仕様なので、移動後もiCloud上にファイルを残しておきたい場合は、移動処理が完了した後に、ローカルからiCloudへcopyItemして戻す処理が必要になります。

私の場合、iCloud同期のスイッチをオンにした時にiCloudからローカルにファイルコピーしようとしているので、iCloud Driveのデータが消えてしまうと、iCloud同期をONにしている他端末からもデータが消えてしまうことになり、対応が必要でした。

iCloudからローカルにダウンロード、その後ローカルからiCloudに再アップロード、なので無駄なトラフィックが発生するし、すごく効率の悪いことをしている感じで嫌だったので、

他の方法として、iCloudからダウンロード前のファイルを、ファイル名末尾の”.icloud”を条件に検索してローカルへstartDownloadingUbiquitousItemでダウンロードしてからcopyItemする力技も試しましたが、

私のアプリでは想定しているファイルサイズが小さかったのと、頻繁に実行される処理ではないこと、そしてiCloud専用処理を使ったコードのシンプルさと動作の信頼感の高さで、今回はsetUbiquitousを使うことにしました。

以下がコードです。引数のremoveは、ユーザーにiCloud上のデータを残すか確認してtrue/falseを設定できるようにしています。

// Documents Directory URL
let fm = FileManager.default
let localUrl = fm.urls(for: .documentDirectory, in: .userDomainMask)[0] // Local Document Directory
let iCloudUrl = fm.url(forUbiquityContainerIdentifier: iCloudContainerId)!.appendingPathComponent("Documents") // iCloud Document Directory
// iCloud->ローカル ファイル移動
// iCloudのデータを削除するか、残すかはユーザーが選択する (remove=falseの場合、残す)
func moveFilesToLocal(remove: Bool) {
    DispatchQueue.main.async {
        let fromUrl = iCloudUrl
        let toUrl = localUrl
        let enumerator = fm.enumerator(atPath: fromUrl.path)
        while let file = enumerator?.nextObject() as? String {
            do {
                let fromFileUrl = fromUrl.appendingPathComponent(file)
                let toFileUrl = toUrl.appendingPathComponent(file)
                try fm.setUbiquitous(false, itemAt: fromFileUrl, destinationURL: toFileUrl) // ローカルへ転送し、iCloudを削除
                print("iCloud->ローカル:ファイル移行に成功しました")
            } catch {
                print("iCloud->ローカル:ファイル移行に失敗しました <File: \(file)> <Error: \(error.localizedDescription)>")
            }
        }
        do {
            if remove == false {
                try fm.copyItem(atPath: toUrl.path, toPath: fromUrl.path) // setUbiquitousにより削除されたiCloudのデータを、ローカルのデータで元に戻す
            }
        } catch {
            print("iCloud->ローカル:ファイル移行に失敗しました <Error: \(error.localizedDescription)>")
        }
    }
}
スポンサーリンク

アプリの宣伝

上記のiCloud同期を実装したアプリがこちらです!よかったら使ってみてください☺️

‎ルーレット - 優柔不断にさようなら
‎■ 特徴 ① 2択〜5択のルーレットがすぐに回せます ・項目入力不要!すぐ結果を出したいときに便利です。 ② 豊富なカスタマイズ ・お好きな画像をルーレットの背景に設定できます。 ・ルーレット項目やボタンの色を自由に設定可能です。 ・回転...

コメント

タイトルとURLをコピーしました