如何将 CoreData 模型项传递到视图中进行编辑

eri*_*icg 2 core-data ios swift swiftui

我在CDPassingQ有一个最小的示例项目

我的主要(ContentView)看起来像:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment( \.managedObjectContext ) private var viewContext
    
    @FetchRequest( sortDescriptors: [ NSSortDescriptor( keyPath: \Item.name, ascending: true ) ],
                   animation:       .default )
    private var items: FetchedResults<Item>
    
    var body: some View {
        NavigationView {
            List {
                ForEach( items ) { item in
                    NavigationLink {
                        NameViewer( itemID: item.objectID )
                    } label: {
                        Text( item.name! )
                    }
                }
                .onDelete( perform: deleteItems )
            }
            .toolbar {
                ToolbarItem( placement: .navigationBarTrailing ) {
                    EditButton()
                }
                
                ToolbarItem {
                    Button() {
                        print( "Add Item" )
                    } label: {
                        NavigationLink {
                            NameViewer();
                        } label: {
                            Label( "Add Item", systemImage: "plus" )
                        }
                    }
                }
            }
        }
    }
    
    
    
    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
            
            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}
Run Code Online (Sandbox Code Playgroud)

NameViewer看起来像

import SwiftUI
import CoreData

enum TrustReason: String, Identifiable, CaseIterable
{
    var id: UUID
    {
        return UUID();
    }
    
    case unknown         = "Unknown";
    case legalOnly       = "Legal Only";
    case goodLabeling    = "Good Labeling";
    case facilityClean   = "Facility Clean";
    case detailedAnswers = "Detailed Answers";
    case unresponsive    = "Unresponsive";
}



extension TrustReason
{
    var title: String
    {
        switch self
        {
            case .unknown:
                return "Unknown";
                
            case .legalOnly:
                return "Legal Only";
                
            case .goodLabeling:
                return "Good Labeling";
                
            case .facilityClean:
                return "Facility Clean";
                
            case .detailedAnswers:
                return "Detailed Answers";
                
            case .unresponsive:
                return "Unresponsive";
        }
    }
}



struct NameViewer: View {
    @Environment( \.presentationMode )     var         presentationMode
    @Environment( \.managedObjectContext ) private var moc
    
    @State private var name: String = ""
    @State private var reason: TrustReason = .unknown

    var itemID: NSManagedObjectID?
    
    var body: some View {
        Form {
            Section( header: Text( "Information" ) ) {
                TextField( "Name", text: $name )
            }
            
            Section( header: Text( "Trust" ) ) {
                Picker( "Reason", selection: $reason ) {
                    ForEach( TrustReason.allCases ) { trustReason in
                        Text( trustReason.title ).tag( trustReason )
                    }
                }
            }
        }
        .toolbar {
            Button() {
                if ( saveName() ) {
                    self.presentationMode.wrappedValue.dismiss()
                }
            } label: {
                Text( "Save" )
            }
        }
        .onAppear {
            print( "on appear" )
            
            guard let theID = itemID,
                  let item = moc.object( with: theID ) as? Item else {
                      return
                  }
            
            print( "passed guard" )
            
            if let itemName = item.name {
                name = itemName
            }
            
            print( name )
        }
    }
    
    
    
    private func saveName() -> Bool {
        let item = Item( context: moc )
        
        do {
            print( self.name )
            
            item.name = self.name
            
            try moc.save()
            
            return true
        } catch {
            print( error )
            print( error.localizedDescription )
        }
        
        self.moc.rollback();
        
        return false
    }
}



struct NameViewer_Previews: PreviewProvider {
    static var previews: some View {
        NameViewer()
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以创建要显示在 ContentView 列表中的新项目。

然后,当我选择列表中的某个项目时,我将该项目传递给 NameViewer。我可以确认我在 .onAppear 代码中成功找到了正确的对象。

然而,存在两个问题:

  1. 如果我在列表中选择一个项目,则项目名称不会出现在名称文本字段中,除非我先单击文本字段。

  2. 使用 .onAppear 似乎不是放置该代码的正确位置。原因是选择器将另一个视图推送到堆栈上,一旦选择了该项目,.onAppear 就会再次运行,并且我会丢失对名称字段的更改名称。

如何更改代码来解决这些问题?

Gen*_*ich 8

为了实现所需的功能,我将更改 UI 和核心数据方面的架构。

\n

在用户界面方面,最好使用导航链接来显示静态数据详细视图,并使用模态来执行数据操作,例如创建和编辑对象。因此,使用一个视图来显示对象详细信息(例如NameViewer),使用另一个视图来编辑对象(例如NameEditor)。此外,将NSManagedObject子类的属性直接绑定到 SwiftUI 控件。不要\xe2\x80\x99t 创建额外的@State属性,然后复制这些值。你\xe2\x80\x99引入了共享状态,SwiftUI 就是要消除这种共享状态。

\n

在核心数据方面,为了执行创建和更新操作,您需要使用子上下文。每当您\xe2\x80\x99创建或更新对象时,都会显示注入了子上下文的模式编辑器视图。这样,如果我们\xe2\x80\x99对我们的更改不满意,我们可以简单地忽略模态,并且更改会被神奇地丢弃,而无需调用rollback(),因为该子上下文会随视图一起被破坏。由于您\xe2\x80\x99 现在正在使用子上下文,所以不要\xe2\x80\x99 忘记将主视图上下文保存在某处,例如当用户导航出应用程序时。

\n

因此,为了在代码中实现这一点,我们需要一些结构来存储新创建的对象以及它们的子上下文:

\n
struct CreateOperation<Object: NSManagedObject>: Identifiable {\n    let id = UUID()\n    let childContext: NSManagedObjectContext\n    let childObject: Object\n    \n    init(with parentContext: NSManagedObjectContext) {\n        let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n        childContext.parent = parentContext\n        let childObject = Object(context: childContext)\n        \n        self.childContext = childContext\n        self.childObject = childObject\n    }\n}\n\nstruct UpdateOperation<Object: NSManagedObject>: Identifiable {\n    let id = UUID()\n    let childContext: NSManagedObjectContext\n    let childObject: Object\n    \n    init?(\n        withExistingObject object: Object,\n        in parentContext: NSManagedObjectContext\n    ) {\n        let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n        childContext.parent = parentContext\n        guard let childObject = try? childContext.existingObject(with: object.objectID) as? Object else { return nil }\n        \n        self.childContext = childContext\n        self.childObject = childObject\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

UI代码如下:

\n
struct ContentView: View {\n    @Environment(\\.managedObjectContext) private var viewContext\n    @FetchRequest(\n        sortDescriptors: [NSSortDescriptor(keyPath: \\Item.name, ascending: true)], animation: .default\n    ) private var items: FetchedResults<Item>\n    @State private var itemCreateOperation: CreateOperation<Item>?\n    \n    var body: some View {\n        NavigationView {\n            List {\n                ForEach(items) { item in\n                    NavigationLink {\n                        NameViewer(item: item)\n                    } label: {\n                        Text(item.name ?? "")\n                    }\n                }\n            }\n            .toolbar {\n                ToolbarItemGroup(placement: .navigationBarTrailing) {\n                    EditButton()\n                    Button(action: {\n                        itemCreateOperation = CreateOperation(with: viewContext)\n                    }) {\n                        Label("Add Item", systemImage: "plus")\n                    }\n                }\n            }\n            .sheet(item: $itemCreateOperation) { createOperation in\n                NavigationView {\n                    NameEditor(item: createOperation.childObject)\n                        .navigationTitle("New Item")\n                }\n                .environment(\\.managedObjectContext, createOperation.childContext)\n            }\n        }\n    }\n}\n\nstruct NameViewer: View {\n    @Environment(\\.managedObjectContext) private var viewContext\n    @State private var itemUpdateOperation: UpdateOperation<Item>?\n    \n    @ObservedObject var item: Item\n    \n    var body: some View {\n        Form {\n            Section {\n                Text(item.name ?? "")\n            }\n        }\n        .navigationTitle("Item")\n        .toolbar  {\n            Button("Update") {\n                itemUpdateOperation = UpdateOperation(withExistingObject: item, in: viewContext)\n            }\n        }\n        .sheet(item: $itemUpdateOperation) { updateOperation in\n            NavigationView {\n                NameEditor(item: updateOperation.childObject)\n                    .navigationTitle("Update Item")\n            }\n            .environment(\\.managedObjectContext, updateOperation.childContext)\n        }\n    }\n}\n\nstruct NameEditor: View {\n    @Environment(\\.dismiss) private var dismiss\n    @Environment(\\.managedObjectContext) private var childContext\n    \n    @ObservedObject var item: Item\n    \n    var body: some View {\n        Form {\n            Section(header: Text("Information")) {\n                if let name = Binding($item.name) {\n                    TextField("Name", text: name)\n                }\n            }\n        }\n        .toolbar {\n            Button() {\n                try? childContext.save()\n                dismiss()\n            } label: {\n                Text("Save")\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更多信息请参见我的相关回答:

\n\n