我正在尝试创建一个可以将值用作@Binding的编辑表单,对其进行编辑并提交更改。在这种情况下,我使用@FetchRequest属性包装器填充了一个包含Core Data记录的列表。我想点击一行以从“列表”导航到“详细信息”视图,然后在“详细信息”视图中导航至“编辑”视图。
我尝试不使用@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()
的东西是超级越野车。
答案 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 以供进一步参考。