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

Core data SwiftUI-将@Binding与核心数据NSManagedObject一起使用?,core-data,swiftui,Core Data,Swiftui,我正在尝试制作一个编辑表单,可以将一个值作为@Binding,编辑它,并提交更改。在本例中,我使用@FetchRequest属性包装器用核心数据记录填充列表。我想点击一行,从列表导航到局部视图,然后在局部视图上导航到编辑视图 我尝试在没有@Binding的情况下执行此操作,代码将编译,但当我进行编辑时,它不会反映在前面的屏幕上。似乎我需要使用@Binding,但我无法找到一种方法来获取列表或ForEach中的NSManagedObject实例,并将其传递给可以将其用作@Binding的视图 列

我正在尝试制作一个编辑表单,可以将一个值作为@Binding,编辑它,并提交更改。在本例中,我使用@FetchRequest属性包装器用核心数据记录填充列表。我想点击一行,从列表导航到局部视图,然后在局部视图上导航到编辑视图

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

列表视图 编辑视图
struct TimelineEditView:视图{
@环境(\.managedObjectContext)变量managedObjectContext
@环境(\.presentationMode)变量presentationMode:绑定
@国家私有变量newDataValue=“”
@绑定var时间线:时间线
var body:一些观点{
返回VStack{
TextField(“要编辑的数据”,文本:self.$newDataValue)
.shadow(颜色:。次,半径:1,x:0,y:0)
.textFieldStyle(RoundedBorderTextFieldStyle())
奥纳佩尔先生{
self.newDataValue=self.timeline.name??“”
}.padding()
垫片()
}
.航海术语(
领先:
按钮(操作:({
//取消模式表
self.newDataValue=“”
self.presentationMode.wrappedValue.discouse()文件
})) {
文本(“取消”)
},
拖尾:按钮(操作:({
self.timeline.name=self.newDataValue
做{
请尝试self.managedObjectContext.save()
}抓住{
打印(错误)
}
//取消模式表
self.newDataValue=“”
self.presentationMode.wrappedValue.discouse()文件
})) {
文本(“完成”)
}
)
}
}

我应该提到的是,我尝试这样做的唯一原因是因为modal
.sheet()
的东西是超级错误。

@Binding
只适用于结构

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

伪代码:

//传递值和初始化子视图
列表(模板){中的模板
TemplateCell(模板:Binding.constant(模板))//init
}
结构模板单元{
@绑定变量模板:用于自动重新加载单元格的模板//@Binding
}
//文本字段+编辑
TextField(“Content”,text:(Binding($template.Content)?Binding.constant(“”),onEditingChanged:{isEditing in
//键盘隐藏时将CoreData保存在此处
},电子委员会:{
//按enter键,可以插入内容
})
//结合
.onReceive(self.template.objectWillChange){in
//我能做点什么
}

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

let childContext=NSManagedObjectContext(并发类型:.mainQueueConcurrencyType)
childContext.parent=viewContext
让childItem=childContext.object(带:objectID)为!项目
返回ItemHost(项目:childItem)
.environment(\.managedObjectContext,childContext)
完成更改后,只需保存子上下文,更改将被推送到主视图上下文,并可以立即保存或稍后保存,具体取决于您的体系结构。如果我们对所做的更改不满意,我们可以在传递子对象时,通过调用子上下文上的
refresh(\uu:mergeChanges:)
放弃这些更改

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

扩展绑定{
func optionalProxy()->绑定?其中值==可选{
guard let value=self.wrappedValue else{return nil}
返回绑定(
获取:{
价值
},
设置:{
self.wrappedValue=$0
}
)
}
}
我们现在可以使用绑定,只要name属性在托管对象模型中设置了默认的空字符串,否则就会崩溃

TextField(“Title”,text:$item.Title.optionalProxy()!)

通过这种方式,我们可以干净地实现没有共享状态的SwiftUI理念。我已经提供了供进一步参考。

是否要在任何单字符文本更改或用户按下按钮时保存?我要导航到具有各种数据输入控件的视图
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")
                }
        )

    }
}