Realm から取得した複数要素も freeze() 指定して使うことで、List 等にも使えます。
ただし、この2つを組み合わせようとすると 少し工夫が必要となります。その工夫を説明します。
Realm シリーズ第4回です。以前の回は、以下です。



Sponsor Link
これまでの復習
ここまでみてきたところで、以下のことがわかっています。
- Realm の Object は、@ObservedObject で受けられる
- List に渡すときは、freeze() 指定しないと、エラーになる
詳細ビューで変更するパターンへの対応
リストによって要素が列挙されている画面から、1要素をクリックして詳細画面へ遷移して、要素の編集を行うパターンは、定番の操作です。
これまでの方法では、このパターンを実装しようとするとエラーが発生してしまいます。
これまでの延長線上での実装
List に渡すときには、freeze() して渡さないといけません、また、Realm の Object は、@ObservedObject で受けられるので、
普通に作ると以下のように実装出来そうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
struct ContentView: View { @ObservedObject var appModel: AppModel @ObservedObject var textLines: RealmSwift.List<TextLine> var body: some View { VStack { NavigationView { List { ForEach( textLines.filter("askedForDelete == false").freeze() ) { textLine in // (1) ナビゲーション先で編集予定 NavigationLink(textLine.text, destination: TextDetailView(textLine: textLine.id))) // Text("\(textLine.askedForDelete.description) \(textLine.id) \(textLine.text)").font(.footnote) } .onDelete(perform: delete) .onMove(perform: move) } .navigationBarItems(leading: HStack { Button(action: { self.appModel.undo() }, label: { Image(systemName: "arrow.uturn.left.circle") }) .disabled(self.appModel.canUndo != true) Button(action: { self.appModel.redo() }, label: { Image(systemName: "arrow.uturn.right.circle") }) .disabled(self.appModel.canRedo != true) }, trailing: HStack { EditButton() Button("Add", action: { let newName = "name" + String(Int.random(in: 0...2000)) let createCommand = CreateTextLineCommand(appModel: self.appModel, newTextLineText: newName) self.appModel.executeCommand(createCommand) }) } ) .navigationBarTitle("Realm Doc") } HStack { Text("# of item \(textLines.filter("askedForDelete == false").count)") } } } /* snip */ } // (2) struct TextDetailView: View { // (3) @ObservedObject var textLine: TextLine var body: some View { TextField(textLine.text, text: $textLine.text) } } |
(1) で、下位ビューに移行するようにして、(2) で定義している下位ビューで、テキストを編集できるようにしています。
変更を伝播するため、(3) で @ObservedObject で受けています。
実行すると・・・
以下のようなエラーが発生します。
1 |
Exception NSException * "Frozen Realms do not change and do not have change notifications." |
理由は、以下の箇所で (a) で freeze() して渡した要素を、(b) で Binding して渡そうとしている点です。
1 2 3 4 5 6 7 |
// ⬇️ (a) ForEach( textLines.filter("askedForDelete == false").freeze() ) { textLine in // ⬇️ (b) NavigationLink(textLine.text, destination: textLine) } |
Realm のドキュメントには、以下のように説明されています。
- Opening a write transaction on a frozen realm.
- Modifying a frozen object.
- Adding a change listener to a frozen realm, collection, or object.
解決策も以下のように書かれています。
そのままですね。再度、Query しなさいとのことでした。
再 Query するように修正
準備として id から要素を取得する以下のような関数を作ります。
1 2 3 4 5 6 |
func textLineFromID(id: String) -> TextLine { let realm = try! Realm() return realm.object(ofType: TextLine.self, forPrimaryKey: id)! } |
そして、TextDetailView へ渡す TextLine を、再取得してから渡すようにします。
1 2 3 4 5 6 |
ForEach( textLines.filter("askedForDelete == false").freeze() ) { textLine in NavigationLink(textLine.text, destination: TextDetailView(textLine: self.textLineFromID(id: textLine.id))) } |
改めて実行してみると
別のエラーが・・・・
1 |
Exception NSException * "Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first." |
TextField に String の Binding を渡して変更しようとしているのですが、Realm では、Object の変更は、以下のように、write ブロックで変更しなければいけません。
1 2 3 4 5 |
try! realm.write { // Realm 要素の変更処理等 } |
TextField には、onCommit: で編集終了時の処理を記述する方法がありますので、以下のようにします。
1 2 3 4 5 6 7 8 |
TextField(textLine.text, text: $tmpText, onCommit: { let realm = try! Realm() try! realm.write { textLine.text = tmpText } }) |
やっと・・・
SwiftUI の期待するやり方と、Realm の期待するやり方をうまく整合させることが必要になりましたが、スムーズに動作させることができました。
参考までに、改めて、コードを提示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
struct ContentView: View { @ObservedObject var appModel: AppModel @ObservedObject var textLines: RealmSwift.List<TextLine> var body: some View { VStack { NavigationView { List { ForEach( textLines.filter("askedForDelete == false").freeze() ) { textLine in NavigationLink(textLine.text, destination: TextDetailView(textLine: self.textLineFromID(id: textLine.id))) } .onDelete(perform: delete) } .navigationBarItems(leading: HStack { Button(action: { self.appModel.undo() }, label: { Image(systemName: "arrow.uturn.left.circle") }) .disabled(self.appModel.canUndo != true) Button(action: { self.appModel.redo() }, label: { Image(systemName: "arrow.uturn.right.circle") }) .disabled(self.appModel.canRedo != true) }, trailing: HStack { EditButton() Button("Add", action: { let newName = "name" + String(Int.random(in: 0...2000)) let createCommand = CreateTextLineCommand(appModel: self.appModel, newTextLineText: newName) self.appModel.executeCommand(createCommand) }) } ) .navigationBarTitle("Realm Doc") } HStack { Text("# of item \(textLines.filter("askedForDelete == false").count)") } } } func delete(at offsets:IndexSet) { let target = textLines.filter("askedForDelete == false")[offsets.first!] let deleteCommand = DeleteTextLineCommand(appModel: self.appModel, targetId: target.id, newValue: true) self.appModel.executeCommand(deleteCommand) } } func textLineFromID(id: String) -> TextLine { let realm = try! Realm() return realm.object(ofType: TextLine.self, forPrimaryKey: id)! } } struct TextDetailView: View { @ObservedObject var textLine: TextLine @State private var tmpText = "" var body: some View { TextField(textLine.text, text: $tmpText, onCommit: { let realm = try! Realm() try! realm.write { textLine.text = tmpText } }) } } |
まとめ
- Realm の Object は、Observable なので、そのまま使える
- SwiftUI の List に渡すには、freeze() して渡す
- freeze した Realm Object は、再度 Query してから、変更する
- Realm Object は、Realm.write ブロックで変更する必要があるので、onCommit 等を使用して、自分で変更処理を行う
SwiftUI の学習のお供
SwiftUI は、GUI のライブラリでもあるので、どのようなコンポーネントかを視覚的に確認できると学習がはかどります。
以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、超便利です。
英語ですが、画面のスナップショットが多くありますので、英語を読む必要は少ないと思います。
説明は以上です。
不明な点やおかしな点ありましたら、ご連絡いただけるとありがたいです。
Sponsor Link