I have looked at other questions and answers but I still cannot set the managedObjectContext of a UITableViewController embedded in a UINavigationController. Here is my current code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UINavigationController *navigationController = [sb instantiateViewControllerWithIdentifier:#"Learn"];
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
However when I run the code, the app crashes and produces a log output of this:
+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name
When I try just logging NSLog(#"%#", self.managedObjectContext); in the MasterViewController the result is `(null)
I created my managedObjectContext like so and it received no errors:
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
N.B. I am trying to set a UITableViewController that is not the initial view controller
Any ideas? Thanks in advance!
If you are using the boilerplate Apple template (from Xcode), your app delegate should have a method like this, creating the managed objects dynamically when you call self.managedObjectContext:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
Put a breakpoint there and check that you pass the correct context to the view controller. Make sure the VC property is (nonatomic, strong).
If your navigation controller is set as the initial controller in the storyboard, then the problem is that you're instantiating a new instance of that navigation controller with instantiateViewControllerWithIdentifier. You should get the reference to MasterViewController like this instead:
MasterViewController *controller = (MasterViewController *)[(UINavigationController *)self.window.rootViewController topViewController];
Related
I am following a tutorial to develop an iOS app. I am using core data. The first view of the app is RootViewController. All Core Data stack is on the AppDelegate file. This is the part of the code from AppDelegate.m that makes the call to the RootViewController file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Fetch the data to see if we ought to pre-populate
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self loadFavoriteThingsData];
RootViewController *rootViewController = (RootViewController *)[navigationController topViewController];
[rootViewController setManagedObjectContext:[self managedObjectContext]];
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
return YES;
}
Now, in another part of the app I need to open a new view Controller that is a duplicate of RootViewController, called DoneViewController, but using other NSPredicates to show other core data objects.
In RootViewController there is a button to open the MenuViewController file, from there I try to open DoneViewController using following method:
- (IBAction)doneToDoaction:(id)sender {
DoneViewController *viewController = [[DoneViewController alloc] init];
[self presentViewController:viewController animated:YES completion:nil];
}
but an exception is fired:
[MenuViewController managedObjectContext]: unrecognized selector sent to instance 0x145a0c00
2013-12-26 22:58:23.688 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MenuViewController managedObjectContext]: unrecognized selector sent to instance
I guess I have to pass the managedObjectContext from RootViewController to MenuViewController and then from MenuViewController to DoneViewController, but I don't know how to do it.
This is due to that you are not using NSObject class. For this you have to implement NSObject in you AppDelegate.h.Like this..
#interface AppDelegate : UIResponder <UIApplicationDelegate,NSObject>{
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
and also add the properties.
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)applicationDocumentsDirectory;
Also add code in AppDelegate.m file...
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil] ;
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"<Project Name>.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error])
{
/*Error for store creation should be handled in here*/
}
return persistentStoreCoordinator;
}
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
This works for me 100%. Hope so that it work for you.
There are few solutions to do what you want.
The first is to expose the context from the application delegate as #Gyanendra commented. The second is to pass the context from controller to controller. For further references I really suggest to read PASSING AROUND A NSMANAGEDOBJECTCONTEXT ON IOS by Marcus Zarra.
Anyway, I would prefer the second solution. Passing around the context or grabbing by an instance of NSManagedObject. Based on the latter each NSManagedObjectInstance has a reference to the context has been registered in.
[managedObjectInstance managedObjectContext];
These solutions prevent to have a rigid application and avoid to pollute the application delegate.
In your case, you could just follow this approach. From RootViewController pass the context to MenuViewController and then, from the latter, pass it to DoneViewController. How?
Simple enough. Just expose properties like for the controller you are interested in (MenuViewController and DoneViewController in this case).
#property (nonatomic, strong) NSManagedObjectContext* mainContext;
that can be set as
// from RootViewController
menuViewController.mainContext = [self managedObjectContext];
When in Xcode 5 I am using the code below, which is in the #interface area, to get data from my CoreData model. It is telling me that I should insert a semi-colon here,
- (NSManagedObjectContext *)managedObjectContext;
Any help is appreciated!
Below is my full code block.
//Get Data
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
When in Xcode 5 I am using the code below, which is in the #interface area, to get data from my CoreData model.
This is you problem, you're only supposed to but declarations in the #interface. Actual implementations go in the #implementation section.
Your interface goes
#interface
- (NSManagedObjectContext *)managedObjectContext;
#end
In you implementation you put the actual method:
#implementation
...
//Get Data
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate respondsToSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
...
#end
UPDATE:
Thanks to Abizern for noticing that there was another bug in your code: the line [delegate performSelector:#selector(managedObjectContext)] should actually be [delegate respondsToSelector:#selector(managedObjectContext)], because otherwise the application will crash if it doesn't respond to the selector.
you have weird getter in my opinion, try ti use something like this one
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil)
{
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
I strive to understand Core Data implementation.
When I examined Core Data implementations I encountered codes as above
In AppDelegate.m
#synthesize managedObjectContext = __managedObjectContext;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSManagedObjectContext *context = [self managedObjectContext];
..
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
There is a property as
#synthesize managedObjectContext = __managedObjectContext;
and there is a func as
- (NSManagedObjectContext *)managedObjectContext
How it's create a relation between them, how/where it's call managedObjectContext func.
__managedObjectContext is an instance variable.
managedObjectContext is a getter method for __managedObjectContext. It allows you to get __managedObjectContext by, e.g., self.managedObjectContext.
Access of instance variable is usually done through its setter and getter methods.
I'm trying to add Core Data to an existing project. I've:
1)added the Core Data framework
2)added the accessors and properties to the AppDelegate
3)created the data model file
Now when I try to call
NSManagedObjectContext *context = [self managedObjectContext];
from a view controller the context is nil and the managedObjectContext never fires.
Here is the AppDelegate:
#import "XXXAppDelegate.h"
#import <CoreData/CoreData.h>
#implementation XXXAppDelegate
#synthesize window=_window;
#synthesize navigationController=_navigationController;
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
// Explicitly write Core Data accessors
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"<Project Name>.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:nil error:&error]) {
/*Error for store creation should be handled in here*/
}
return persistentStoreCoordinator;
}
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
#end
EDIT: here is my view controller code
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *cardSet = [NSEntityDescription insertNewObjectForEntityForName:#"CardSet" inManagedObjectContext:context];
[cardSet setValue:#"Set 1" forKey:#"cardSetName"];
Try adding these condition to check if your managedObjectContext is nil or not wherever you want to use it. If its nil copy it from Appdelegate file.
if (managedObjectContext == nil)
{
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}
Assuming your properties are declared... you are synthesizing them to ivars with an underscore in front. That's a good thing. However, the only place you want to access them with the underscore is in the implementation of the getter/setter for the property. Unfortunately, that's not happening in any of these. Change it to...
- (NSManagedObjectContext *) managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"<Project Name>.sqlite"]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:nil error:&error]) {
/*Error for store creation should be handled in here*/
}
return _persistentStoreCoordinator;
}
If you look at the Master-Detail Application template in Xcode you see they pass the ManagedObjectContext in the AppDelegate like this:
#import "AppDelegate.h"
#import "MasterViewController.h"
#implementation AppDelegate
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
NSLog(#"navigationController viewControllers: %#",[navigationController viewControllers]);
NSLog(#"navigationController.topViewController: %#",navigationController.topViewController);
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
If you need a TabBarViewController in front of your app the code looks like this:
#import "AppDelegate.h"
#import "MasterViewController.h"
#implementation AppDelegate
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UITabBarController *tabController = (UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController = (UINavigationController *)[[tabController viewControllers] objectAtIndex:0];
MasterViewController *controller = (MasterViewController *)[[navigationController viewControllers] objectAtIndex:0];
controller.managedObjectContext = self.managedObjectContext;
navigationController = (UINavigationController *)[[tabController viewControllers] objectAtIndex:1];
controller = (MasterViewController *)[[navigationController viewControllers] objectAtIndex:0];
controller.managedObjectContext = self.managedObjectContext;
navigationController = (UINavigationController *)[[tabController viewControllers] objectAtIndex:2];
controller = (MasterViewController *)[[navigationController viewControllers] objectAtIndex:0];
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
I don't know how you got past without compiler errors, but your #synthesizes use underscore'd variables that your getters never access. Do it like this:
- (NSManagedObjectContext *) managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return _managedObjectContext;
}
Take note of _managedObjectContext (with underscore). Do the same fixes with managedObjectModel and persistentStoreCoordinator.
I'm having some problems in passing context from the app delegate to the view controller.
I've found many tutorials on the internet, and all suggest to use the didFinishLaunchingWithOptions method to create the view controller, set the context property and push it.
My problem is that I want to use storyboard, and the view controller is created and pushed within it, and not in the app delegate.
I've tried to do this in my app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//instantiate local context
NSManagedObjectContext *context = [self managedObjectContext];
if (!context)
{
// Handle the error.
NSLog(#"Error: Context is null");
}
//reference the view controller
helloCoreDataViewController1_001 *rootViewController = [helloCoreDataViewController1_001 alloc];
// Pass the managed object context to the view controller
rootViewController.managedObjectContext = context;
return YES;
}
and this in my view controller:
#implementation helloCoreDataViewController1_001
#synthesize name, address, phone, status, managedObjectContext;
//....
- (IBAction)saveContact
{
NSLog(#"name: %#",self.name.text);
NSLog(#"address: %#",self.address.text);
NSLog(#"phone: %#",self.phone.text);
//Save the new instance of the contact entity
Contact *contact = (Contact *)[NSEntityDescription insertNewObjectForEntityForName:#"Contacts" inManagedObjectContext:managedObjectContext];
[contact setContactName:[NSString stringWithFormat:#"%#",self.name.text]];
[contact setValue:self.address.text forKey:#"address"];
[contact setContactPhone:[NSString stringWithFormat:#"%#",self.phone.text]];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
// Handle the error.
NSLog(#"error: %#", error.description);
self.status.text = #"Error: contact NOT saved";
}
else
self.status.text = #"Contact saved";
}
When I debug, I can see that in the app delegate, the context is populated correctly, and also the property in the view controller is ok.
But when my saveContact method is invoked, the context is empty.
Do you have any suggestions about this? How can I pass the context to the view controller with storyboard?
You can get the Storyboard's rootviewcontroller in didFinishLaunchingWithOptions by accessing
self.window.rootViewController
instead of allocing a new one.
So those two lines in your didFinishLaunchingWithOptions should look like this:
helloCoreDataViewController1_001 *rootViewController = (helloCoreDataViewController1_001 *)self.window.rootViewController;
rootViewController.managedObjectContext = context;
Instead of passing your managed obeject context to the view controller try to get it on the view controller from the appDelegate:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
if (managedObjectContext == nil) {
id appDelegate = (id)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [appDelegate managedObjectContext];
}... //finish your fetch request of course...
}
With me it worked beautifully