SwiftUI - 将 @Binding 与核心数据 NSManagedObject 一起使用?

rad*_*dev 19 core-data swiftui

我正在尝试制作一个编辑表单,该表单可以将值作为 @Binding 进行编辑,然后提交更改。在这种情况下,我使用 @FetchRequest 属性包装器填充包含核心数据记录的列表。我想点击一行从列表导航到详细信息视图,然后在详细信息视图上导航到编辑视图。

示例图像

我尝试在没有 @Binding 的情况下执行此操作,代码将编译,但是当我进行编辑时,它不会反映在之前的屏幕上。似乎我需要使用@Binding,但我无法找到一种方法来在 List 或 ForEach 中获取 NSManagedObject 实例,并将其传递给可以将其用作 @Binding 的视图。

列表显示

struct TimelineListView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    // The Timeline class has an `allTimelinesFetchRequest` function that can be used here
    @FetchRequest(fetchRequest: Timeline.allTimelinesFetchRequest()) var timelines: FetchedResults<Timeline>

    @State var openAddModalSheet = false

    var body: some View {

        return NavigationView {
            VStack {
                List {

                    Section(header:
                        Text("Lists")
                    ) {
                        ForEach(self.timelines) { timeline in

                            // ?? How to I use the timeline in this list as a @Binding?

                            NavigationLink(destination: TimelineDetailView(timeline: $timeline)) {
                                TimelineCell(timeline: timeline)
                            }
                        }
                    }
                    .font(.headline)

                }
                .listStyle(GroupedListStyle())

            }

            .navigationBarTitle(Text("Lists"), displayMode: .inline)

        }

    } // End Body
}
Run Code Online (Sandbox Code Playgroud)

详细视图

struct TimelineDetailView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    @Binding var timeline: Timeline

    var body: some View {

        List {

            Section {

                NavigationLink(destination: TimelineEditView(timeline: $timeline)) {
                    TimelineCell(timeline: timeline)
                }

            }

            Section {

                Text("Event data here")
                Text("Another event here")
                Text("A third event here")

            }


        }.listStyle(GroupedListStyle())

    }
}
Run Code Online (Sandbox Code Playgroud)

编辑视图

struct TimelineEditView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State private var newDataValue = ""

    @Binding var timeline: Timeline

    var body: some View {

        return VStack {

            TextField("Data to edit", text: self.$newDataValue)
                .shadow(color: .secondary, radius: 1, x: 0, y: 0)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .onAppear {
                    self.newDataValue = self.timeline.name ?? ""
            }.padding()
            Spacer()
        }

            .navigationBarItems(
                leading:
                Button(action: ({
                    // Dismiss the modal sheet
                    self.newDataValue = ""
                    self.presentationMode.wrappedValue.dismiss()

                })) {
                    Text("Cancel")
                },

                trailing: Button(action: ({

                    self.timeline.name = self.newDataValue


                    do {
                        try self.managedObjectContext.save()
                    } catch {
                        print(error)
                    }

                    // Dismiss the modal sheet
                    self.newDataValue = ""
                    self.presentationMode.wrappedValue.dismiss()

                })) {
                    Text("Done")
                }
        )

    }
}
Run Code Online (Sandbox Code Playgroud)

我应该提到,我什至尝试这样做的唯一原因是因为模态的.sheet()东西是超级马车。

Gen*_*ich 13

要使用 Core Data 实现创建和编辑功能,最好使用嵌套的托管对象上下文。如果我们注入一个从主视图上下文派生的子托管对象上下文,以及与子上下文关联的正在创建或编辑的托管对象,我们将获得一个安全的空间,我们可以在其中进行更改并在需要时丢弃它们,而无需改变驱动我们 UI 的上下文。

\n
    let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n    childContext.parent = viewContext\n    let childItem = childContext.object(with: objectID) as! Item\n    return ItemHost(item: childItem)\n        .environment(\\.managedObjectContext, childContext)\n
Run Code Online (Sandbox Code Playgroud)\n

完成更改后,我们只需要保存子上下文,更改将被推送到主视图上下文,并且可以立即或稍后保存,具体取决于您的架构。如果我们对我们的更改不满意,我们可以通过调用rollback()子上下文来丢弃它们。

\n
    childContext.rollback()\n
Run Code Online (Sandbox Code Playgroud)\n

关于将托管对象与 SwiftUI 视图绑定的问题,一旦我们将子对象注入到编辑表单中,我们就可以将其属性直接绑定到 SwiftUI 控件。这是可能的,因为NSManagedObject类符合ObservableObject协议。我们所要做的就是标记一个包含对子对象的引用的属性,@ObservedObject然后获取它的绑定。这里唯一的复杂之处是经常存在类型不匹配的情况。例如,托管对象将字符串存储为String?,但TextField期望为String。为了解决这个问题,我们可以使用 Binding\xe2\x80\x99s 初始值设定项init?(_\xc2\xa0base:\xc2\xa0Binding<Value?>)

\n

我们现在可以使用我们的绑定,前提是 name 属性在托管对象模型中设置了默认的空字符串,否则将会崩溃。

\n
    TextField("Title", text: Binding($item.title)!)\n
Run Code Online (Sandbox Code Playgroud)\n

这样我们就可以干净地实现无共享状态的 SwiftUI 哲学。我提供了一个示例项目以供进一步参考。

\n


nin*_*nes 6

@Binding 仅适用于结构。

但 CoreData 结果是 Objects(NSManagedObject采用ObservableObject)。您需要使用@ObservedObject来注册更改。

  • 您能详细说明一下如何实现这一目标吗?我尝试将详细视图中的对象时间线标记为 @ObservedObject,但更改不会传播到父视图。我还尝试保存上下文,每次详细视图中发生更改,但父视图仍然没有更新。 (3认同)