SwiftUI 2.0 / CoreData 2021:没有错误但实体未保存

SwiftUI 2.0 / CoreData 2021: No error but entity not saved

我有一个 SwiftUI 2.0 应用程序,它使用 CoreData 在本地存储数据,尽管 preview 模式可以在内存中写入数据,也可以不写入内存(我都测试过)当我尝试将数据保存到 CoreData 时版本查看,我没有收到任何错误消息,但数据没有写入模拟器的 sqlite 数据文件...我做错了什么?

我在一对多关系中有 2 个实体:

Post:

Post图片:

我提取了以下主要文件:

MyApp.swift

import SwiftUI

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            MyNavigationView().environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

Persistence.swift

import Foundation
import SwiftUI
import CoreData
import CloudKit

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for day in 1...10 {
            let newPost = Post(context: viewContext)
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy/MM/dd"
            newPost.date = dateFormatter.date(from: "2021/02/\(String(format: "%02d", day))")!
            newPost.id = UUID()
            newPost.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
            newpost.timestamp = Date()
            for index in 1...7 {
                let newPostPic = PostPic(context: viewContext)
                if let image = UIImage(named:newPostPic.filename!) {
                    newPostPic.data = image.pngData()
                }
                newPost.addToFiles(newPostPic)
            }
        }
        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)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer
    private static var _model: NSManagedObjectModel?
    private static func model(name: String) throws -> NSManagedObjectModel {
        if _model == nil {
            _model = try loadModel(name: name, bundle: Bundle.main)
        }
        return _model!
    }
    private static func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
        guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
            throw CoreDataError.modelURLNotFound(forResourceName: name)
        }

        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            throw CoreDataError.modelLoadingFailed(forURL: modelURL)
       }
        return model
    }

    enum CoreDataError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }

    init(inMemory: Bool = false) {
     
        container = NSPersistentCloudKitContainer(name: "curum")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}

MyNavigationView.swift

struct MyNavigationView: View {
    @Environment(\.managedObjectContext) private var viewContext
    var body: some View {
        NavigationView {
            TabView { // tab bar at the bottom of the screen
                HomeView()
                    .tabItem{
                        Image(systemName: "house.fill")
                    }.tag(0)
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("MyApp")
        }
    }
}

struct MyNavigationView_Previews: PreviewProvider {
    static var previews: some View {
        MyNavigationView()
    }
}

HomeView.swift

//
//  HomeView.swift
//  curum
//
//  Created by loic on 2021/02/19.
//

import SwiftUI

struct HomeView: View {
    var body: some View {
        ZStack {
            // ...
            VStack { // the plus button
                Spacer()
                HStack {
                    Spacer()
                    NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)) {
                        ZStack {
                            Circle()
                                .frame(width: 80, height: 80)
                            Image(systemName: "plus")
                                .renderingMode(.template)
                                .font(.system(size: 60, weight: .light))
                        }
                    }
                    .padding(.trailing, 16)
                    .padding(.bottom, 16)
                }
            }
        }

    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

EditionView.swift

import Foundation
import SwiftUI
import CoreData

struct EditionView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.presentationMode) var mode: Binding<PresentationMode>
    @State private var postText: String = ""
    @State private var postImage: Image = Image("image1")
    @State private var showingImagePicker = false
    @State private var inputImage: UIImage?
    
    var body: some View {
        VStack {
                TextEditor(text: $postText)
                    .lineSpacing(10)
                    .border(Color.gray)
                    .padding(.all)
            HStack {
                Button(action: {
                    self.showingImagePicker = true
                }) {
                    
                    if (inputImage == nil) {
                        Text("Pick Image")
                    } else {
                        postImage
                            .resizable()
                            .renderingMode(.original)
                            .frame(width: 100, height: 100)
                    }
                }.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
                    ImagePicker(image: self.$inputImage)
                }
            }
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: Button(action : {
            self.mode.wrappedValue.dismiss()
        }){
            Image(systemName: "xmark")
        },
        trailing: Button {
                let newPost = Post(use: viewContext)
                newPost.date = Date()
                newPost.id = UUID()
                newPost.text = entryText
                newPost.timestamp = Date()
                let newPostPic = PostPic(use: viewContext)
                newPostPic.data = inputImage!.pngData()
                newPost.addToFiles(newPostPic)
                do {
                    try viewContext.save()
                    mode.wrappedValue.dismiss()
                } catch {
                    let nsError = error as NSError
                    fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                }
            } label: {
                Image(systemName: "checkmark.circle.fill")
            })
    }
    func loadImage() {
        guard let inputImage = inputImage else { return }
        postImage = Image(uiImage: inputImage)
    }
}

struct EditionView_Previews: PreviewProvider {
    static var previews: some View {
        EditionView()
    }
}

struct ImagePicker: UIViewControllerRepresentable {
    @Environment(\.presentationMode) var presentationMode
    @Binding var image: UIImage?

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker

        init(_ parent: ImagePicker) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                parent.image = uiImage
            }

            parent.presentationMode.wrappedValue.dismiss()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

    }
}

** 编辑以回应@Tushar Sharma** 我忘了补充一点,我将以下扩展添加到 NSManagedObject 以消除 :

中的错误
import Foundation
import CoreData

public extension NSManagedObject {
    convenience init(use context: NSManagedObjectContext) {
        let name = String(describing: type(of: self))
        let entity = NSEntityDescription.entity(forEntityName: name, in: context)!
        self.init(entity: entity, insertInto: context)
    }
}

抱歉,这是 HomeView.swift 文件中的简单复制粘贴问题:

在接下来的行中,我使用了 preview 持久化上下文 ...

NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)) {

... 而不是使用 shared 一个:

NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)) {

解决了我的问题!