【问题标题】:iCloud on off button in SwiftUI results in a crashSwiftUI 中的 iCloud 开关按钮导致崩溃
【发布时间】:2021-03-11 22:00:44
【问题描述】:

我已将 iCloud 添加到我的 SwiftUI 应用程序中,一切似乎都运行良好,但是我需要为它实现一个开关切换。搜索后,我发现一些论坛帖子建议在关闭 icloud 时重新创建容器。代码如下:

lazy var persistentContainer: NSPersistentContainer = {
    return setupContainer()
}()

/* This is called when the iCloud setting is turned on and off */
func refreshCoreDataContainer() {
    /* Save changes before reloading */
    try? self.persistentContainer.viewContext.save()
    /* Reload the container */
    self.persistentContainer = self.setupContainer()
}

private func setupContainer() -> NSPersistentContainer {
    let useCloudSync = UserSettings.shared.enableiCloudSync
    let container: NSPersistentContainer!

    /* Use the icloud container if the user enables icloud, otherwise use the regular container */
    if useCloudSync {
        container = NSPersistentCloudKitContainer(name: "App")
    } else {
        container = NSPersistentContainer(name: "App")
        let description = container.persistentStoreDescriptions.first
        description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
    }
    container.persistentStoreDescriptions.first?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

    /* Load the data */
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    })
    return container
}

问题是,一旦我重新加载容器,应用程序就会崩溃并出现以下错误:

Thread 1: "executeFetchRequest:error: A fetch request must have an entity."
Multiple NSEntityDescriptions claim the NSManagedObject subclass 'App.ColorCollection' so +entity is unable to disambiguate.
'ColorCollection' (0x60f000022000) from NSManagedObjectModel (0x607000067260) claims 'App.ColorCollection'.
`

我认为崩溃与 SwiftUI 保持对旧容器的引用有关。创建窗口时,它使用环境将容器传递给它:

let contentView = MyContentView().environment(\.managedObjectContext, persistentContainer.viewContext)

所以我尝试关闭窗口,重新加载容器,然后在下面重新创建窗口,但应用仍然崩溃。

func refreshCoreDataContainer() {
    windowController.window?.close()

    /* Save changes before continuing */
    try? self.persistentContainer.viewContext.save()
    self.persistentContainer = self.setupContainer()

    self.createAndShowMainWindow()
}

如何在 SwiftUI 中实现 iCloud 切换而不导致崩溃?

【问题讨论】:

标签: swift macos cocoa core-data swiftui


【解决方案1】:

SwiftUI 2.0 & IOS 14.3

我现在正在解决这个确切的问题。这是我使用这两个资源一起困惑的答案:

Using Core Data with SwiftUI 2.0 and Xcode 12

CoreData+CloudKit | On/off iCloud sync toggle

如果有人找到更好的答案,请告诉我!

final class PersistentContainer {
    
    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 CoreDataModelError.modelURLNotFound(forResourceName: name)
        }

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

    enum CoreDataModelError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }
 
    public static func getContainer(iCloud: Bool) throws -> NSPersistentContainer {
        let name = "YOUR APP" // Put your model name here
        if iCloud {
            return NSPersistentCloudKitContainer(name: name, managedObjectModel: try model(name: name))
        } else {
            return NSPersistentContainer(name: name, managedObjectModel: try model(name: name))
        }
    }
}


class CoreDataManager {

    static let shared = CoreDataManager()

    lazy var persistentContainer: NSPersistentContainer = {
        setupContainer()
    }()
    
    func refreshCoreDataContainer() {
        try? self.persistentContainer.viewContext.save()
        self.persistentContainer = self.setupContainer()
    }
    
    private func setupContainer() -> NSPersistentContainer {
        
        let iCloud = UserDefaults.standard.bool(forKey: "iCloudOn") // Replace with your UserDefaults boolean here
            
        do {
            let newContainer = try PersistentContainer.getContainer(iCloud: iCloud)
            guard let description = newContainer.persistentStoreDescriptions.first else { fatalError("No description found") }
            
            if iCloud {
                newContainer.viewContext.automaticallyMergesChangesFromParent = true
                newContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
            } else {
                description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            }

            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

            newContainer.loadPersistentStores { (storeDescription, error) in
                if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
            }
            
            return newContainer
            
        } catch {
            print(error)
        }
        
        fatalError("Could not setup Container")
    }
}

然后使用只需将其放入您的App.swift 文件中:

let persistenceController = CoreDataManager.shared

这在App.swiftvar body

.environment(\.managedObjectContext, persistenceController.persistentContainer.viewContext)

要使用,请将其放在您的切换开关上

.onChange(of: toggleVariable) { value in
    CoreDataManager.shared.refreshCoreDataContainer()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-21
    • 2020-03-05
    • 1970-01-01
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多