はじめに
ForEachの表示のためにResultsを配列に変換したり、Realmクラスと同じ構造の表示専用構造体を用意したくなかったので使い回しができるようにしてみました (ただし、RealmのListは非対応)。
** 2021/05/10 追記: 追加された@ObservedResultsと@ObservedRealmObjectを使えば「これより以下読まなくてOKです」の内容は不要です。**
@ObservedResults
@Stateや@Binding、@ObservedObjectのような感じで検索結果を指定します。onDeleteや異なるViewの変更にも対応しています。
@ObservedResults(MyEvent.self,filter:NSPredicate(format: "title != 'L'"),sortDescriptor:SortDescriptor(keyPath: "start", ascending: false)) private var results
body内:
ForEach(results){
data in
/*ここにView*/
}.onDelete(perform: $results.remove)
$をつけると書き込みトランザクションが自動で開きます。
@ObservedRealmObject
RealmSwiftのListに対してonDeleteやonMoveが使えるようになります。
@ObservedRealmObject var parent:Series
onDeleteやonMoveでは$をつけてください!
つけない場合は書き込みトランザクション(realm.write{})を追加してください
ForEach(parent.books){
book in
/*ここにView*/
}.onDelete(perform: $parent.books.remove).onMove(perform: $parent.books.move)
参考: https://github.com/realm/realm-cocoa/blob/v10.7.5/RealmSwift/SwiftUI.swift#L273-L345
これより以下読まなくてOKです
参考
この記事は、
SwiftUIのListでRealmのデータを削除するとクラッシュする時の直し方
を参考にさせていただきました。
コード
注意
- Realmのデータを定義しているクラスにObjectKeyIdentifiableプロトコルを付ける必要があります。
- 凍結したResultsから取得したデータにrealm.writeブロックを使う処理(データの変更や削除)をする場合はObservableResultsのwriteで処理する必要があります。
//View
let results = try! Realm().objects(Test.self)
@ObservedObject private var data:ObservableResults<Test> = ObservableResults(results)
/*List(data.freezeDataUI){略} Listの表示にはfreezeDataUIを使用します*/
//例:削除する場合
let d = data.freezeDataUI.first()! //d:Testクラス(凍結)
data.write(d){//d->objへ解凍
obj in
let realm = try! Realm()
try? realm.write{
realm.delete(obj)
}
}
class ObservableResults<T:Object & ObjectKeyIdentifiable>:ObservableObject{
@Published var freezeDataUI:Results<T>!
private var meltData:Results<T>!
init(_ results:Results<T>) {
meltData = results
freezeDataUI = meltData.freeze()
}
func write(_ obj:T,block:(_:T)->()){
if let r = meltData.first(where: {
$0.id == obj.id//ObjectKeyIdentifiableのid
}){
block(r)
reload()
}
}
func reload(){
freezeDataUI = meltData.freeze()
//self.objectWillChange.send()
}
}
追記(2021/2/11) RealmのListについて
追記の追記: @ObservedRealmObjectを使えばOKです
RealmのListを使用する場合、List内に同じデータが複数あるとSwiftUIのForEachはそれらを同じものと判断するため、上の方法だとonDeleteやonMoveでデータを動かす時に表示がおかしくなります。
下記のようにListのインデックスでid指定した構造体を作れば対応できますがこうなるとただの配列なのでいちいち上のような方法を取らなくていいような...
let freezeListUI = freezeDataUI.enumerated().map{ListObject(id: $0.offset, obj: $0.element)}
struct ListObject<T:Object>:Identifiable {
var id: Int//Index
var obj:T
}
# おわりに
初めてQiitaで記事を書きました。すごい遠回りなやり方をしている気がするのでもっといい感じの方法がありましたらぜひコメントで教えてください。
変更
2021/02/11: 追記と記事名を一部変更しました("SwiftUIのList"→"ForEach")
2021/05/10: @ObservedResultsと@ObservedRealmObjectについて追記