CoreDataのFetchRequestで得られた要素を別ビューにBinding相当で渡したい時

タイトル長くなってます(汗

CoreDataのデータを@FetchRequestで取得して、その要素を別ビューに編集用に渡したい時ありますよね。
その時に、どうやって渡せば良いかのメモです。

MEMO
CoreDataでは以下のようなモデルを作り、適当なWrappedPropertyを作っています。

  • Recordは、CoreDataのEntity
  • volumeは、Recordのプロパティ

前提のContentView

以下のContentViewがあって、DetailViewという子ViewにRecordを渡して、詳細を編集できるようにします。
問題は、DetailViewにどうやって要素を渡すのか? という点です。

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Record.entity(), sortDescriptors: [
        NSSortDescriptor(keyPath: \Record.date, ascending: false)
    ]) var records: FetchedResults

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(records.indices, id: \.self) { index in
                        NavigationLink(destination: DetailView(record: ????), label: {
                            Text("\(self.records[index].formattedDate) \(self.records[index].formattedVolume) ")
                        })
                    }
                }
                Button(action: {
                    self.addSample()
                }, label: {
                    Text("Add Sample")
                })
            }
            .navigationBarTitle("Water Record")
        }
    }
    
    func addSample() {
        let today = DateInRegion(Date(), region: Region.current).dateAtStartOf(.day)

        let record8am = Record.init(context: self.moc)
        record8am.date = today.dateByAdding(8, .hour).date
        record8am.volume = 30
        
        let record12pm = Record.init(context: self.moc)
        record12pm.date = today.dateByAdding(12, .hour).date
        record12pm.volume = 50

        let record7pm = Record.init(context: self.moc)
        record7pm.date = today.dateByAdding(19, .hour).date
        record7pm.volume = 90
        
        try? self.moc.save()
        
    }
    
}

@FetchRequestの要素はBindingで渡せない

@Bindingで受けようとすると、受け側のビュー(DetailView)で、以下のように定義すると思います。

struct DetailView: View {
    @Environment(\.managedObjectContext) var moc
    @Binding var record:Record
    var body: some View {
        return VStack {
            Text("\(record.formattedDate)")
            Text("Volume: \(record.formattedVolume)")
            Slider(value: $record.volume, in: 0...100, step: 5)
        }
    }
}

渡す側(ContentView)では、以下のように渡すと思うのですが、エラーになります。

DetailView(record: self.$records[index])

NSManagedObjectは、ObservableObjectでした

NSManagedObjectは、ObservableObjectで、各プロパティは、@Published相当らしいです。
@Observedで受けようとして受け側のビュー(DetailView)で、以下のように定義してみました。

struct DetailView: View {
    @Environment(\.managedObjectContext) var moc
    @ObservedObject var record:Record // BindingではなくObservedObject
    
    var body: some View {
        return VStack {
            Text("\(record.formattedDate)")
            Text("Volume: \(record.formattedVolume)")
            Slider(value: $record.volume, in: 0...100, step: 5)
        }
    }
}

渡す側(ContentView)では、以下のように渡すと期待通りの動きになります。

DetailView(record: self.records[index])

テストしたコード

XCode11.4を使って、以下のコードで動作を確認しました。
ContentView.swift

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Record.entity(), sortDescriptors: [
        NSSortDescriptor(keyPath: \Record.date, ascending: false)
    ]) var records: FetchedResults

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(records.indices, id: \.self) { index in
                        NavigationLink(destination: DetailView(record: self.records[index]), label: {
                            Text("\(self.records[index].formattedDate) \(self.records[index].formattedVolume) ")
                        })
                    }
                }
                Button(action: {
                    self.addSample()
                }, label: {
                    Text("Add Sample")
                })
            }
            .navigationBarTitle("Water Record")
        }
    }
    
    func addSample() {
        let today = DateInRegion(Date(), region: Region.current).dateAtStartOf(.day)

        let record8am = Record.init(context: self.moc)
        record8am.date = today.dateByAdding(8, .hour).date
        record8am.volume = 30
        
        let record12pm = Record.init(context: self.moc)
        record12pm.date = today.dateByAdding(12, .hour).date
        record12pm.volume = 50

        let record7pm = Record.init(context: self.moc)
        record7pm.date = today.dateByAdding(19, .hour).date
        record7pm.volume = 90
        
        try? self.moc.save()
    }
}

DetailView.swift

struct DetailView: View {
    @Environment(\.managedObjectContext) var moc
    @ObservedObject var record:Record
    
    var body: some View {
        return VStack {
            Text("\(record.formattedDate)")
            Text("Volume: \(record.formattedVolume)")
            Slider(value: $record.volume, in: 0...100, step: 5)
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です