ggworean ggworean - 7 months ago 10
Swift Question

Managed Context from CoreDataStack isn't persisting through the navigationbar

I'm trying to get a contacts page set up where I can add/edit contacts.
However, I'm getting a nil value when I try to call my CoreDataStack.context.

Here it originates in the AppDelegate

lazy var coreDataStack = CoreDataStack()


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
UINavigationBar.appearance().barTintColor = navigationMainColor
UINavigationBar.appearance().tintColor = navigationMainItemColor

let navigationController = window!.rootViewController as! UINavigationController
let listViewController = navigationController.topViewController as! ViewController
listViewController.coreDataStack = coreDataStack

return true
}


I pass it to my first view controller and inside the rootViewController I have a button that sends me to another view controller. In the rootViewController the CoreDataStack context is still there when I call print(coreDataStack). So I know it exists in the first view. The button I press to go to the contacts page is a bar button on a toolbar.

Possible problem 1 may be that I'm not moving to and from with the navigation bar, however all the views are embedded in a navigation stack.

Do I need to prepareForSegue inside the first view controller in order to pass the NSManagedObjectContext?

There is also a navigation controller between contacts and new contact view controller. But the nil seems to be returned before that even occurs.
In the contacts view controller the prepareForSegue that checks if the user is trying to add a contact is found here

else if segue.identifier == "AddContact" {
let navigationController = segue.destinationViewController as! UINavigationController
let addViewController = navigationController.topViewController as! NewContactViewController
let contactEntity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: coreDataStack.context)

let newContact = Contact(entity: contactEntity!, insertIntoManagedObjectContext: coreDataStack.context)

addViewController.contact = newContact
addViewController.context = newContact.managedObjectContext
addViewController.delegate = self
}


And the error I am getting is that the coreDataStack.context when I try and create a new contact is returning nil. I've been chugging away at this for hours and hours and I think I'm missing something really small or big. Either way I just want to learn Core Data and get better.

Answer

Do I need to prepareForSegue inside the first view controller in order to pass the NSManagedObjectContext?

Yes, this is exactly the problem you are having. You need to do your dependency injection yourself since Apple cannot predict where you want things :)

If your incoming (destination) view controller is wrapped in a navigation controller you can safely assume that point that the topViewController is your view controller (and you can test this) and inject it.

So simply get the navigation controller and then get the destination controller? Is it simply a matter of destinationcontroller.context = self.context in the prepareforsegue for the first view controller?

The other way round:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guard let destinationNavController = segue.destinationViewController as? UINavigationController else {
        print("Unexpected view controller: \(seque.destinationViewController.self)")
    }
    guard let myViewController = destinationNavController.topViewController as? MyViewController else {
        print("Unexpected view controller: \(destinationNavController.topViewController.self)")
    }
    myViewController.context = context
}

Replace pieces with your class names as needed.

Yeah I figured, now I'm getting an odd error that I can't force downcast my ContactViewController to UINavigationController. Is it possible that my ContactViewController isn't on the navigation stack?

ok, step back. If you are just pop'ing view controllers in a nav stack then you are getting your ContactViewController directly as the destination and you can skip the nav controller like this:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guard let myViewController = segue.destinationViewController as? MyViewController else {
        print("Unexpected view controller: \(destinationNavController.topViewController.self)")
    }
    myViewController.context = context
}

If the segue is pointing to a NEW UINavigationController then there is something else wrong.