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

# はじめに
~~ForEachの表示のためにResultsを配列に変換したり、Realmクラスと同じ構造の表示専用構造体を用意したくなかったので使い回しができるようにしてみました (ただし、RealmのListは非対応)。~~

**　2021/05/10 追記: 追加された@ObservedResultsと@ObservedRealmObjectを使えば「これより以下読まなくてOKです」の内容は不要です。**

### @ObservedResults
@Stateや@Binding、@ObservedObjectのような感じで検索結果を指定します。onDeleteや異なるViewの変更にも対応しています。

``` swift
@ObservedResults(MyEvent.self,filter:NSPredicate(format: "title != 'L'"),sortDescriptor:SortDescriptor(keyPath: "start", ascending: false)) private var results
```
body内:

``` swift
ForEach(results){
            data in
            /*ここにView*/
            }.onDelete(perform: $results.remove)
```
$をつけると書き込みトランザクションが自動で開きます。
### @ObservedRealmObject
RealmSwiftのListに対してonDeleteやonMoveが使えるようになります。

``` swift
@ObservedRealmObject var parent:Series
```

**onDeleteやonMoveでは$をつけてください!**
つけない場合は書き込みトランザクション(realm.write{})を追加してください

``` swift
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のデータを削除するとクラッシュする時の直し方](https://qiita.com/yokirin/items/588d3fdeb3610a639dc7)
を参考にさせていただきました。

# コード
#### 注意
+ Realmのデータを定義しているクラスにObjectKeyIdentifiableプロトコルを付ける必要があります。
+ 凍結したResultsから取得したデータにrealm.writeブロックを使う処理(データの変更や削除)をする場合はObservableResultsのwriteで処理する必要があります。


```swift
//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)
                    }
                }
```

```swift
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指定した構造体を作れば対応できますがこうなるとただの配列なのでいちいち上のような方法を取らなくていいような...

```swift
let freezeListUI = freezeDataUI.enumerated().map{ListObject(id: $0.offset, obj: $0.element)}
```

```swift
struct ListObject<T:Object>:Identifiable {
    var id: Int//Index
    var obj:T
}
```
#　おわりに
初めてQiitaで記事を書きました。すごい遠回りなやり方をしている気がするのでもっといい感じの方法がありましたらぜひコメントで教えてください。

# 変更
2021/02/11: 追記と記事名を一部変更しました("SwiftUIのList"→"ForEach")
2021/05/10: @ObservedResultsと@ObservedRealmObjectについて追記