【Swift】RealmファイルをiCloudにバックアップ・リストアする

RealmファイルをiCloudにバックアップ・リストアする方法を挙げます。この辺りの環境設定とコードの記載がある記事は、あまりないため、まとめました。

[実行環境]

Xcode13.1
Realm 10.19.0(SwiftPMから導入)

[手順]

手順は以下となります。
1). 事前準備
Realmを導入した後、何かしらデータを登録してある状態とします。

2). iCloudの環境設定
2-1). apple developer program側の設定
 apple developer programで「Certificates, Identifiers & Profiles」のサイドメニューで、以下の画面で、画面右側の虫眼鏡マークの右横を「iCloud Containers」変更をして 「identifiers」の右のプラスマークからiCloud Containerの登録をします。(ご注意:追加したiCloud Containerは消せないので、登録はタイプミスがないようにご注意ください。削除ができないことは以下の記事にもあります。https://developer.apple.com/forums/thread/5547


iCloud Containerの登録で「identifire名」が重要です。登録作業を進めると以下の画面が表示されます。画面の「identifire名」欄には、「We recommend using a reverse-domain name style string (i.e., com.domainname.appname).」とあります。仮に例のように指定するとiCloud.com.domain.appnameと頭に、「iCloud.」が含まれて登録となります。


2-2). Xcode側の設定
2-2-1). signinig&Capabilityの設定
Xcodeの設定で、「signinig&Capability」の+選択から「iCloud」を選択してください。以下の画面のようにiCloudの設定をできる画面が表示されるます。Xcode側でApple Idと紐付け設定済みの場合(※)は、以下の画面の白色で塗りつぶされている箇所には、2-1)で登録してiCloud Contaier名が表示されます。2-1)で、「iCloud.com.domain.appname」と登録してある場合は、同じ文字列が表示されます。この例の場合は、「iCloud.com.domain.appname」の欄にチェックを入れます。

2-2-2). infoの設定
Xcode13より、info.plistはなく、Targetsのinfoタブ欄に設定を追加します。
(参考になる他サイトの記事になります。:https://software.small-desk.com/development/2021/09/08/xcode-no-infoplist-xcode13/)
infoタブ欄に以下画像のようなkeyとvalueを追加します。赤枠部分には、2-1)で設定した例で挙げた文字列「iCloud.com.domain.appname」を入力します。緑の枠には、iCloudで表示するフォルダ名を指定します。
また、NSUbiquitousContainerIsDocumentScopePublic」欄を1(true)に設定します。

iCloud Drive上にアプリから保存させたファイルを表示するためには、「NSUbiquitousContainerIsDocumentScopePublic」を1(true)の設定と2-2-1)からのここまでの設定にて、iCloud.com.domain.appname」のような同じ文字列に設定をしないとiCloud Drive上に保存したファイルが表示されませんでした。



2-3). 端末の設定画面
端末の設定アプリを開き、iCloudの設定で、実行対象のアプリがiCloudの対象になっているか確認をします。





3).実装コード

iCloudにdefault.realmファイルのバックアップとiCloudからアプリのdefault.realをリストアするコードを記載します。
(※注意点があります。バックアップ処理がエラーになることがあります。それは、filesアプリからバックアップ先のディレクトリを手動で削除してしまった場合に、バックアップ処理で保存先が存在しないというエラーになります。その場合、端末の設定アプリにおけるICloud driveを手動でoffにして再度onにしないと書き込みできないようでした。)

// upload Realm file to icloud
func uploadRealmDbToiCloud() {
if !isCloudEnabled(){
return
}
let fileManager = FileManager.default
let realmArchiveURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)!
.appendingPathComponent("Documents")
.appendingPathComponent("default.realm")
do {
if fileManager.fileExists(atPath: realmArchiveURL.path) {
try fileManager.removeItem(at: realmArchiveURL)
}
let realm = try! Realm()
realm.beginWrite()
try realm.writeCopy(toFile: realmArchiveURL)
realm.cancelWrite()
} catch {
print("ERR")
}
}
func isCloudEnabled() -> Bool {
if DocumentsDirectory.iCloudDocumentsURL != nil { return true }
else { return false }
}
// restore Realm file to icloud
func downloadRealmDbFromICloud(){
if !isCloudEnabled() {
return
}
let fileManager = FileManager.default
if let icloudFolderURL = DocumentsDirectory.iCloudDocumentsURL,
let urls = try? fileManager.contentsOfDirectory(at: icloudFolderURL, includingPropertiesForKeys: nil, options: []) {
if let iClouPath = urls.first {
let lastPathComponent = iClouPath.lastPathComponent
print(lastPathComponent)
if lastPathComponent.contains(".realm") {
var isDownloaded = false
while !isDownloaded {
if fileManager.fileExists(atPath: iClouPath.path) {
isDownloaded = true
self.copyFileToLocal()
}
else {
do {
try fileManager.startDownloadingUbiquitousItem(at: iClouPath)
} catch {
print("Unexpected error: \(error).")
}
}
}
}
}
}
}
func copyFileToLocal() {
if isCloudEnabled() {
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
remove(realmURL: DocumentsDirectory.localDocumentsURL)
let fileManager = FileManager.default
let enumerator = fileManager.enumerator(atPath: DocumentsDirectory.iCloudDocumentsURL!.path)
while let file = enumerator?.nextObject() as? String {
do {
try fileManager.copyItem(at: DocumentsDirectory.iCloudDocumentsURL!.appendingPathComponent(file), to: DocumentsDirectory.localDocumentsURL.appendingPathComponent(file))
} catch let error as NSError {
print("Failed to move file to local dir : \(error)")
}
}
}
}
func remove(realmURL: URL) {
let realmURLs = [
realmURL,
realmURL.appendingPathExtension("lock"),
realmURL.appendingPathExtension("note"),
realmURL.appendingPathExtension("management"),
]
for URL in realmURLs {
try? FileManager.default.removeItem(at: URL)
}
}
struct DocumentsDirectory {
static let localDocumentsURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: .userDomainMask).last!
static let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
}
view raw gistfile1.txt hosted with ❤ by GitHub
4). 参考コード

本記事は、以下のstackoverflowのに掲載されたコードを流用させて頂きました。

このブログの人気の投稿

アプリアイコンの素材探し

【SwiftUI】グラスモーフィズムを試してみました

【SwiftUI】LazyVGridについて