Search code examples
iosswiftcore-datansmanagedobject

Initialize managedContect in AppDelegate for other Viewcontroller


I want to initialize managedObject, but I always get this error:

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

The ViewController I want to use Core Data / managedObject is not my launching ViewController, but an other one (PortfolioVC) I am accessing through my launching ViewController.

This is my code:

import Foundation
import CoreData

class DatabaseController {
    private let modelName: String

    init(modelName: String) {
        self.modelName = modelName
    }

    lazy var managedContext: NSManagedObjectContext = {
        return self.storeContainer.viewContext
    }()

    private lazy var storeContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: self.modelName)
        container.loadPersistentStores { (storeDescription, error) in
            if let error = error as NSError? {
                print("Unresolved error \(error), \(error.userInfo)")
            }
        }
        return container
    }()

    func saveContext () {
        guard managedContext.hasChanges else { return }

        do {
            try managedContext.save()
        } catch let error as NSError {
            print("Unresolved error \(error), \(error.userInfo)")
        }
    }
}

This is my AppDelegate:

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    lazy var coreDataStack = DatabaseController(modelName: "Portfolio")

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        guard let navController = window?.rootViewController as? UINavigationController,
            let viewController = navController.topViewController as? PortfolioVC else {
                return true
        }
        viewController.managedContext = coreDataStack.managedContext
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {

    }

    func applicationDidEnterBackground(_ application: UIApplication) {

        coreDataStack.saveContext()
    }

    func applicationWillEnterForeground(_ application: UIApplication) {

    }

    func applicationDidBecomeActive(_ application: UIApplication) {

    }

    func applicationWillTerminate(_ application: UIApplication) {

        coreDataStack.saveContext()
    }


}

This is the code in PortfolioVC:

import UIKit import CoreData

class PortfolioVC: UIViewController {


    var managedContext: NSManagedObjectContext!
    var namePortfolio: NamePortfolio?

override func viewDidLoad() {
        super.viewDidLoad()
        tableViewPortfolio.dataSource = self

        let standardPortfolio = "Watchlist"
        let fetchRequest: NSFetchRequest<NamePortfolio> = NamePortfolio.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(NamePortfolio.name), standardPortfolio)

        do {
            let results = try managedContext.fetch(fetchRequest) // here I get the error (managedContext = nil)
            if results.count > 0 {
                // Portfolio already created
                namePortfolio = results.first

            } else {
                // no Portfolio
                namePortfolio = NamePortfolio(context: managedContext)
                namePortfolio?.name = standardPortfolio
                try managedContext.save()
            }
        }  catch let error as NSError {

            print("Could not fetch \(error), \(error.userInfo)")
        }

    }

Thanks a lot for the help!


Solution

  • When the code in didFinishLaunchingWithOptions runs, the PortfolioVC has not yet been added to the navigation controller stack (or even instantiated), so the guard fails. There are two ways to get the managedContext to the PortfolioVC:

    a) pass it to your launching view controller in didFinishLaunchingWithOptions, then pass it from there to the PortfolioVC using prepareForSegue. You will need to add a managedContext property to your launching VC, even though you only need it to pass on to the Portfolio VC.

    b) in PortfolioVC, get a reference to the AppDelegate and then get the managedContext from there.

        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
            managedContext = appDelegate.coreDataStack.managedContext
        }
    

    Opinions vary as to which of these is the better design pattern.