LoginSignup
2
2

More than 1 year has passed since last update.

【SwiftUI】ForEachでRealmのResultsをそのまま操作する

Last updated at Posted at 2021-01-02

はじめに

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について追記

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2