Core data 如何(或应该)用嵌套的managedObjectContext替换此CoreData SwiftUI应用程序中的MVVM?

Core data 如何(或应该)用嵌套的managedObjectContext替换此CoreData SwiftUI应用程序中的MVVM?,core-data,mvvm,swiftui,Core Data,Mvvm,Swiftui,我已经使用CoreData在SwiftUI应用程序中编写了这个MVVM的小示例,但是我想知道是否有更好的方法来实现这一点,比如使用嵌套的viewcontext 代码的目的是在用户更新所有需要的字段并点击“保存”之前,不要触摸CoreData实体。换句话说,如果用户输入大量属性,然后“取消”,则不必撤消任何字段。但我如何在SwiftUI中实现这一点 当前,viewModel已发布了@个变量,这些变量从实体获取线索,但不绑定到其属性 代码如下: ContentView 此视图相当标准,但下面是列表中

我已经使用CoreData在SwiftUI应用程序中编写了这个MVVM的小示例,但是我想知道是否有更好的方法来实现这一点,比如使用嵌套的viewcontext

代码的目的是在用户更新所有需要的字段并点击“保存”之前,不要触摸CoreData实体。换句话说,如果用户输入大量属性,然后“取消”,则不必撤消任何字段。但我如何在SwiftUI中实现这一点

当前,viewModel已发布了
@个
变量,这些变量从实体获取线索,但不绑定到其属性

代码如下:

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