Core data 如何(或应该)用嵌套的managedObjectContext替换此CoreData SwiftUI应用程序中的MVVM?
我已经使用CoreData在SwiftUI应用程序中编写了这个MVVM的小示例,但是我想知道是否有更好的方法来实现这一点,比如使用嵌套的viewcontext 代码的目的是在用户更新所有需要的字段并点击“保存”之前,不要触摸CoreData实体。换句话说,如果用户输入大量属性,然后“取消”,则不必撤消任何字段。但我如何在SwiftUI中实现这一点 当前,viewModel已发布了Core data 如何(或应该)用嵌套的managedObjectContext替换此CoreData SwiftUI应用程序中的MVVM?,core-data,mvvm,swiftui,Core Data,Mvvm,Swiftui,我已经使用CoreData在SwiftUI应用程序中编写了这个MVVM的小示例,但是我想知道是否有更好的方法来实现这一点,比如使用嵌套的viewcontext 代码的目的是在用户更新所有需要的字段并点击“保存”之前,不要触摸CoreData实体。换句话说,如果用户输入大量属性,然后“取消”,则不必撤消任何字段。但我如何在SwiftUI中实现这一点 当前,viewModel已发布了@个变量,这些变量从实体获取线索,但不绑定到其属性 代码如下: ContentView 此视图相当标准,但下面是列表中
@个变量,这些变量从实体获取线索,但不绑定到其属性
代码如下:
ContentView
此视图相当标准,但下面是列表中的NavigationLink和Fetch:
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Contact.lastName, ascending: true)],
animation: .default)
private var contacts: FetchedResults<Contact>
var body: some View { List {
ForEach(contacts) { contact in
NavigationLink (
destination: ContactProfile(contact: contact)) {
Text("\(contact.firstName ?? "") \(contact.lastName ?? "")")
}
}
.onDelete(perform: deleteItems)
} ///Etc...the rest of the code is standard
大多数viewmodel代码都改编自GRDB Combine示例。所以,我并不总是确定要排除什么。要包括的内容。在发现以下内容后,我选择在这种情况下避免使用viewModel:
moc.refresh(contact, mergeChanges: false)
苹果文档:
因此,您可以抛开ContactViewModel
,保持ContentView
不变,并使用以下功能:
接触剖面图
enum
已成为ContactProfile
视图的扩展
import SwiftUI
import CoreData
struct ContactProfile: View {
@Environment(\.managedObjectContext) private var moc
@ObservedObject var contact: Contact
@State private var isEditing = false
@State private var errorAlertIsPresented = false
@State private var errorAlertTitle = ""
var body: some View {
VStack {
if !isEditing {
Text("\(contact.firstName ?? "") \(contact.lastName ?? "")")
.font(.largeTitle)
.padding(.top)
Spacer()
} else {
Form{
TextField("First Name", text: $contact.firstName ?? "")
TextField("First Name", text: $contact.lastName ?? "")
}
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarBackButtonHidden(isEditing ? true : false)
.navigationBarItems(leading:
Button (action: {
/// This is the key change:
moc.refresh(contact, mergeChanges: false)
withAnimation {
self.isEditing = false
}
}, label: {
Text(isEditing ? "Cancel" : "")
}),
trailing:
Button (action: {
if isEditing { saveContact() }
withAnimation {
if !errorAlertIsPresented {
self.isEditing.toggle()
}
}
}, label: {
Text(!isEditing ? "Edit" : "Done")
})
)
.alert(
isPresented: $errorAlertIsPresented,
content: { Alert(title: Text(errorAlertTitle)) }) }
private func saveContact() {
do {
if contact.firstName!.isEmpty {
throw ValidationError.missingFirstName
}
if contact.lastName!.isEmpty {
throw ValidationError.missingLastName
}
try moc.save()
} catch {
errorAlertTitle = (error as? LocalizedError)?.errorDescription ?? "An error occurred"
errorAlertIsPresented = true
}
}
}
extension ContactProfile {
enum ValidationError: LocalizedError {
case missingFirstName
case missingLastName
var errorDescription: String? {
switch self {
case .missingFirstName:
return "Please enter a first name for this contact."
case .missingLastName:
return "Please enter a last name for this contact."
}
}
}
}
这还需要以下代码,可在此链接中找到:
导入快捷界面
func???(左:绑定,右:T)->绑定{
装订(
获取:{lhs.wrappedValue??rhs},
集合:{lhs.wrappedValue=$0}
)
}
在发现以下情况后,我选择在这种情况下避免使用viewModel:
moc.refresh(contact, mergeChanges: false)
苹果文档:
因此,您可以抛开ContactViewModel
,保持ContentView
不变,并使用以下功能:
接触剖面图
enum
已成为ContactProfile
视图的扩展
import SwiftUI
import CoreData
struct ContactProfile: View {
@Environment(\.managedObjectContext) private var moc
@ObservedObject var contact: Contact
@State private var isEditing = false
@State private var errorAlertIsPresented = false
@State private var errorAlertTitle = ""
var body: some View {
VStack {
if !isEditing {
Text("\(contact.firstName ?? "") \(contact.lastName ?? "")")
.font(.largeTitle)
.padding(.top)
Spacer()
} else {
Form{
TextField("First Name", text: $contact.firstName ?? "")
TextField("First Name", text: $contact.lastName ?? "")
}
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarBackButtonHidden(isEditing ? true : false)
.navigationBarItems(leading:
Button (action: {
/// This is the key change:
moc.refresh(contact, mergeChanges: false)
withAnimation {
self.isEditing = false
}
}, label: {
Text(isEditing ? "Cancel" : "")
}),
trailing:
Button (action: {
if isEditing { saveContact() }
withAnimation {
if !errorAlertIsPresented {
self.isEditing.toggle()
}
}
}, label: {
Text(!isEditing ? "Edit" : "Done")
})
)
.alert(
isPresented: $errorAlertIsPresented,
content: { Alert(title: Text(errorAlertTitle)) }) }
private func saveContact() {
do {
if contact.firstName!.isEmpty {
throw ValidationError.missingFirstName
}
if contact.lastName!.isEmpty {
throw ValidationError.missingLastName
}
try moc.save()
} catch {
errorAlertTitle = (error as? LocalizedError)?.errorDescription ?? "An error occurred"
errorAlertIsPresented = true
}
}
}
extension ContactProfile {
enum ValidationError: LocalizedError {
case missingFirstName
case missingLastName
var errorDescription: String? {
switch self {
case .missingFirstName:
return "Please enter a first name for this contact."
case .missingLastName:
return "Please enter a last name for this contact."
}
}
}
}
这还需要以下代码,可在此链接中找到:
导入快捷界面
func???(左:绑定,右:T)->绑定{
装订(
获取:{lhs.wrappedValue??rhs},
集合:{lhs.wrappedValue=$0}
)
}
NSManagedObject是ObservieObject,并且NSManaged属性已发布,因此您可以根据需要在视图中直接观察CoreData对象。是的,但如果我尝试更改它们,它们似乎会立即保存到实体中,完全通过managedObjectContext传递。NSManagedObject是ObservableObject,并且NSManaged属性已发布,因此您可以在需要时直接在视图中观察CoreData对象。是的,但如果我尝试更改它们,它们似乎会立即保存到实体中,完全通过managedObjectContext传递。
import SwiftUI
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}