SwiftUI-对核心数据NSManagedObject使用@Binding吗?

时间:2019-08-22 17:41:30

标签: core-data swiftui

我正在尝试创建一个可以将值用作@Binding的编辑表单,对其进行编辑并提交更改。在这种情况下,我使用@FetchRequest属性包装器填充了一个包含Core Data记录的列表。我想点击一行以从“列表”导航到“详细信息”视图,然后在“详细信息”视图中导航至“编辑”视图。

Example image

我尝试不使用@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
}

详细信息视图

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())

    }
}

编辑视图

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")
                }
        )

    }
}

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

3 个答案:

答案 0 :(得分:4)

@Binding仅适用于结构。

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

答案 1 :(得分:0)

伪代码:

// pass value & init child view

List(templates) { template in
    TemplateCell(template: Binding.constant(template)) // init 
}

struct TemplateCell {
    @Binding var template: Template // @Binding for reload cell automatic
}

// TextField + Edit

TextField("Content", text: (Binding($template.content) ?? Binding.constant("")), onEditingChanged: { isEditing in
    // save CoreData here when keyboard hide

}, onCommit: {
    // press enter, you can insert something

})

// combine
.onReceive(self.template.objectWillChange) { _ in
    // can do some thing
}

答案 2 :(得分:0)

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

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

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

    childContext.refresh(item, mergeChanges: false)

关于将托管对象与 SwiftUI 视图绑定的问题,一旦我们将子对象注入我们的编辑表单,我们就可以将其属性直接绑定到 SwiftUI 控件。这是可能的,因为 NSManagedObject 类符合 ObservableObject 协议。我们所要做的就是用 @ObservedObject 标记一个包含对我们子对象的引用的属性,然后我们就可以得到它的发布者。这里唯一的复杂之处是经常存在类型不匹配。例如,托管对象将字符串存储为 String?,但 TextField 需要 String。为了解决这个问题,我们可以扩展 Binding 结构并引入一些像这样的代理。

    extension Binding {
        func optionalProxy<Wrapped>() -> Binding<Wrapped>? where Value == Optional<Wrapped> {
            guard let value = self.wrappedValue else { return nil }
            
            return Binding<Wrapped>(
                get: {
                    value
                },
                set: {
                    self.wrappedValue = $0
                }
            )
        }
    }

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

    TextField("Title", text: $item.title.optionalProxy()!)

通过这种方式,我们可以干净利落地实现无共享状态的 SwiftUI 理念。我提供了 a sample project 以供进一步参考。