I'm trying to use CLLocation Manager to get someone's location whenever they move and store the lat, lng, and timestamp into core data, then display that in a table view tab. However, the output in the console always indicates that managedObjectContext is nill by throwing this log coredataproject[12478:11903] After managedObjectContext: <NSManagedObjectContext: 0x7248230>
Here's the relevant code in my AppDelgate implementation file
#import "AppDelegate.h"
#import "RootViewController.h"
#import "FirstViewController.h"
#implementation AppDelegate
#synthesize window;
#synthesize navigationController;
#pragma mark -
#pragma mark Application lifecycle
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Configure and show the window.
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
NSLog(#"Could not create context for self");
}
rootViewController.managedObjectContext = context;
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
/**
applicationWillTerminate: saves changes in the application's managed object context before the application terminates.
*/
- (void)applicationWillTerminate:(UIApplication *)application {
NSError *error;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Handle the error.
}
}
}
Here is the FirstViewController.M code, where I'm getting the location and storing it in the core data 'Event' entity
- (void)viewDidLoad
{
locationManager =[[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[locationManager startUpdatingLocation];
-(void) locationmanager: (CLLocationManager *) manager
didUpdateToLocation: (CLLocation *) newLocation
fromLocation: (CLLocation *) oldLocation
{
CLLocation *location = [locationManager location];
if (!location) {
return;
}
/*
Create a new instance of the Event entity.
*/
RootViewController *rootviewcontroller = [RootViewController alloc];
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:rootviewcontroller.managedObjectContext];
// Configure the new event with information from the location.
CLLocationCoordinate2D coordinate = [location coordinate];
[event setLatitude:[NSNumber numberWithDouble:coordinate.latitude]];
[event setLongitude:[NSNumber numberWithDouble:coordinate.longitude]];
// Should be the location's timestamp, but this will be constant for simulator.
// [event setCreationDate:[location timestamp]];
[event setTimeStamp:[NSDate date]];
// Commit the change.
NSError *error;
if (![rootviewcontroller.managedObjectContext save:&error]) {
NSLog(#"Save Error");
}
//RootViewController *rootviewcontroller = [RootViewController alloc];
[rootviewcontroller.eventsArray insertObject:event atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[rootviewcontroller.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[rootviewcontroller.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
And finally here is the RootViewController file where I'm trying to fetch and display what's in the core data. It's when I click this tab that the console tells me that managedObjectConsole is nill
- (void)viewDidLoad {
[super viewDidLoad];
if (managedObjectContext == nil)
{
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}
// Set the title.
self.title = #"Locations";
/*
Fetch existing events.
Create a fetch request; find the Event entity and assign it to the request; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Order the events by time stamp, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
NSLog(#"Mutable Fetch Results equals nill");
}
// Set self's events array to the mutable array, then clean up.
[self setEventsArray:mutableFetchResults];
}
I'm also doing some more stuff with organizing the table data once it's there, but I don't think that's where the problem is.
I'm unsure as to why nothing would be in managedObjectContext, as it should have the location data from the location manager. I'm not too familiar with Core data, so I'm probably just doing something simple wrong, but any insight would be appreciated!
Your mistake is in the didUpdateToLocation method. There you create a new instance of RootViewController. You just need to save the newLocation to CoreData and need the MOC for that (not the RootViewController). So you need to find a way to pass the MOC to the FirstViewController. You can do this similar like you did in the AppDelegate or like this:
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Why do you reset the MOC of RootViewController in viewDidLoad? You already passed it in applicationDidFinishLaunching!
I'd recommend to use a NSFetchedResultsController for the table view. It automatically detects changes of the data and reloads the table if needed, just implement the delegate correctly. Here is a helpful tutorial on this: http://www.raywenderlich.com/999/core-data-tutorial-how-to-use-nsfetchedresultscontroller
Related
I'm using RestKit 0.20. I'm trying to download the json response from the local rails application and save it to core-data. I tried to follow https://github.com/RestKit/RKGist/blob/master/TUTORIAL.md and everything worked fine.
I wanted to use this many times so i made it a base class(ListController) and subclassed it to SessionListController and EventListController. The mapping is provided in AppDelegate.m.
The app has a first viewcontroller which is the root controller which always maps the responses and everything works fine. But as soon as i change the viewcontroller i will get the response and the operation stops. it does't even say if the mapping has started. I'm not sure what i have missed.
Here i'm initialising restkit
AppDelegate.m
#import "AppDelegate.h"
#import <UIKit/UIKit.h>
#implementation AppDelegate
RKEntityMapping *eventEntityMapping,*sessionEntitiyMapping;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"event" ofType:#"momd"]];
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
[self setManagedObjectStore: [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel]];
// Initialize the Core Data stack
[self.managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *persistentStore = [self.managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add persistent store: %#", error);
[self.managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:self.managedObjectStore];
NSString *url=#"http://192.168.11.11:3000";
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:url]];
objectManager.managedObjectStore = self.managedObjectStore;
[RKObjectManager setSharedManager:objectManager];
[self mapEntities];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:eventEntityMapping pathPattern:#"/api/v1/events" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
RKResponseDescriptor *responseDescriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:sessionEntitiyMapping pathPattern:#"/api/v1/sessions" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor2];
// Override point for customization after application launch.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UINavigationController *navigationController = [storyboard instantiateViewControllerWithIdentifier:#"mainCenterController"];
IIViewDeckController* secondDeckController = [[IIViewDeckController alloc] initWithCenterViewController:navigationController
leftViewController:[storyboard instantiateViewControllerWithIdentifier:#"sideBarController"]];
secondDeckController.centerhiddenInteractivity=IIViewDeckCenterHiddenNotUserInteractiveWithTapToClose;
self.window.rootViewController = secondDeckController;
return YES;
}
-(void)mapEntities{
//event
eventEntityMapping = [RKEntityMapping mappingForEntityForName:#"Event" inManagedObjectStore:self.managedObjectStore];
[eventEntityMapping addAttributeMappingsFromDictionary:#{
#"id": #"event_id",
#"name": #"name",
#"description": #"desc",
#"no_of_days": #"no_of_days",
#"start_date": #"start_date",
}];
eventEntityMapping.identificationAttributes = #[ #"event_id" ];
sessionEntitiyMapping = [RKEntityMapping mappingForEntityForName:#"Session" inManagedObjectStore:self.managedObjectStore];
[sessionEntitiyMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"description": #"desc",
#"start_time": #"start_time",
#"duration": #"duration",
#"location_id": #"location_id",
#"event_id": #"event_id",
#"id": #"session_id",
}];
sessionEntitiyMapping.identificationAttributes = #[ #"session_id" ];
}
Base class where i'm calling getObjectsAtPath for RKManager depending on the base class.
ListController.m
#implementation ListController
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectStore.mainQueueManagedObjectContext;
[self loadLocs];
[self.refreshControl beginRefreshing];
}
-(void)setModel:(Models)model{
if(self.model!=model){
_model=model;
self.title=[Model displayFor:self.model];
_fetchedResultsController = nil;
[self.tableView reloadData];
[self loadLocs];
[self.refreshControl beginRefreshing];
}
}
- (void)loadLocs
{
[[RKObjectManager sharedManager] getObjectsAtPath:[Model listApiFor:self.model]
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.refreshControl endRefreshing];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
[self.refreshControl endRefreshing];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"An Error Has Occurred" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
NSLog(#"error: %#",error);
[alertView show];
}];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[[self fetchedRCforTableView:tableView] sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath forTableView:tableView];
return cell;
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:[Model entityFor:self.model] inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:[Model sortDescriptorFor:self.model] ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:[Model displayFor:self.model]];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"_fetchedResultsController count %d",_fetchedResultsController.fetchedObjects.count);
return _fetchedResultsController;
}
#end
This is one of the base classes where i set the model name
EventViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.model=Eventm;
// Do any additional setup after loading the view.
}
SessionViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.model=Sessionm;
// Do any additional setup after loading the view.
}
Full code is at https://github.com/leohemanth/event-app
After using
RKLogConfigureByName("RestKit", RKLogLevelWarning);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
I can see that, i'm getting proper Http response but it is not being mapped. Which i'm not sure why?
I've just updated my Restkit package to 0.20.2. I'm not facing the problem now. It should be fixed in the update.
I have an app that creates a local notification. When the app is closed (i.e. not running or in the background), it crashes when being launched from the notification. I've managed to figure out which line is crashing the app (stated in a comment below):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
// Initialise the main view
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
// Initialise a Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController];
// Set the ViewController as the rootViewController of the window
self.window.rootViewController = nav;
// Colour the navigation bar
nav.navigationBar.tintColor = [UIColor colorWithRed:0.07f green:0.59f blue:0.94f alpha:1];
// Set the background
if (isPhone568) {
self.window.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"santa_back5.png"]];
}
else {
self.window.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"santa_back.png"]];
}
[self.window makeKeyAndVisible];
[nav release];
self.viewController.managedObjectContext = [self managedObjectContext];
application.applicationIconBadgeNumber = 0;
// Handle launching from a notification
UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(dateCreated like %#)",
[localNotif.userInfo objectForKey:#"dateCreated"]];
LetterViewController *letterView = [[LetterViewController alloc] initWithNibName:#"LetterViewController" bundle:nil];
// Get the letter to pass on
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Letter" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSError *error;
//
// THIS NEXT LINE IS CRASHING THE APP
//
NSArray *letters = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
Letter *myLetter = [letters objectAtIndex:0];
letterView.theLetter = myLetter;
//[myLetter release];
// Pass the selected object to the new view controller.
[self.viewController.navigationController pushViewController:letterView animated:YES];
[letterView release];
[fetchRequest release];
}
return YES;
}
I am using the following 3 functions to get the managed object context, persistent store coordinator and managed object model:
//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] retain];
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];
}
Any help in solving this problem would be greatly appreciated.
Thanks in advance.
Couple of things. You appear to be using Core Data on the main thread in what you show, if so all interaction must be on this thread. You should update the code someday to use the new 'perform' API which uses blocks.
Since your localNotif code now does some heavy lifting in the launch delegate it does not return for some time and iOS may kill your app. Take that same code and put it into dispatch block posted to the main queue and it should work. Add some asserts to insure the navigation controller property exists too.
Thanks for the answer. Seems my problem was with the predicate! It doesn't like me using "like" in the where clause. I changed it to an = and it works!
I am trying to test this program on use of Core data. Once again, this is an example from the Dave Marks book. It has four text fields on a view and it loads it by using core data to connect to the database.
The app was created as a window based application and then I added a viewController to it. The file's owner is a sub class of the custom viewController class that I have created.
When I execute it, the UIView comes up with a blank view with no text boxes or labels that I had created in the view.
I put a break point in the main method, it does not even go anywhere from there when I click on step into method button. When I place a break point on the viewDidLoad method, it does not even get to it.
Lastly I do not get any errors on the console. What is going on?
Here is the viewController class:
#import "PersistenceViewController.h"
#import "CoreDataPersistenceAppDelegate.h"
#implementation PersistenceViewController
#synthesize line1;
#synthesize line2;
#synthesize line3;
#synthesize line4;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
CoreDataPersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Line" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error");
}
for (NSManagedObject *oneObject in objects) {
NSNumber *lineNum = [oneObject valueForKey:#"lineNum"];
NSString *lineText = [oneObject valueForKey:#"lineText"];
NSString *fieldName = [NSString stringWithFormat:#"line%d", [lineNum integerValue]];
UITextField *theField = [self valueForKey:fieldName];
theField.text = lineText;
}
[request release];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
-(void) applicationWillResignActive:(NSNotification *)notification {
CoreDataPersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSError *error;
for (int i=1; i<=4; i++) {
NSString *fieldName = [NSString stringWithFormat:#"line%d",i];
UITextField *theField = [self valueForKey:fieldName];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Line" inManagedObjectContext:context];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(lineNum = %d)", i];
[request setPredicate:pred];
NSManagedObject *theLine = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error");
}
if ([objects count] > 0)
theLine = [objects objectAtIndex:0];
else
theLine = [NSEntityDescription insertNewObjectForEntityForName:#"Line" inManagedObjectContext:context];
[theLine setValue:[NSNumber numberWithInt:i] forKey:#"lineNum"];
[theLine setValue:theField.text forKey:#"lineText"];
[request release];
}
[context save:&error];
}
- (void)viewDidUnload
{
self.line1 = nil;
self.line2 = nil;
self.line3 = nil;
self.line4 = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[line1 release];
[line2 release];
[line1 release];
[line1 release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
Be sure to change the viewcontroller that gets intialized in the appdelegate with the correct nib. The blank UIView is probably the view created when you started the project.
In your AppDelegate there is a method called applicationdidFinishLaunchingWithOptions, in which something like the following code is located:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[RIViewController alloc] initWithNibName:#"RIViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
In my case I load the RIViewController. By default the nib file has the same name as the controller and is hence named RIViewController aswell.
I'm a little stumped with an issue that I think goes back to my design.
I'm constructing a TableViewController based on a mainly static set of rows (4) - using that as a basis for a UITableView. Each row will kick off varying different views (detail, and UITableViews)... In my managed object context for the top view I can easily nav to the associated detail view because it's in context (nickname).. I initially thought about simply having unrelated tables and a series of buttons that would fire off the views... But I ditched that idea. My main current issue is knowing how to switch MOC or to different fetchedresults controllers (which are by the way defined in each .m to fetch from different tables.
At the moment, I've got a switch inside of TableViewControler.m.. This snipit works:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger switchval = indexPath.row;
switch (switchval) {
case 0: // Nickname
{
//This version brings up a detailed view controller -
NickNameDetail *controller = [[NickNameDetail alloc] initWithNibName:#"NickNameDetail" bundle:nil];
controller.fetchedResultsController = self.fetchedResultsController;
controller.managedObjectContext = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:controller animated:YES];
break;
}
case 1:
{
//NextTableViewContoller *.... (etc)... here here
...
However, I can't figure out how to switch to a different context and get different rows.
meTableViewController.managedObjectContext = self.managedObjectContext;
nickNameDetail.managedObjectContext = self.managedObjectContext;
nextTableViewController.managedObjectContext = self.managedObjectContext;
Anyone run into a scenario like this before? Sorry, can't get the code formatter to behave.
Thanks!
In some cases it may be good/opportune to pass to the new view controller pushed on the navigation stack the MOC of your current view controller. However, you usually want to pass a newly created MOC. Do this as follows:
in your app delegate add the following method
- (NSManagedObjectContext*)createNewManagedObjectContext
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
return [moc autorelease];
}
in your view controller pass the MOC as follows
myAppDelegate *mainDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainMOC = [mainDelegate createNewManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:mainMOC];
newViewController.managedObjectContext = mainMOC;
and then handle the notification as needed, here is an example
- (void)contextDidSave:(NSNotification *)notification
{
[managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
[self.tableView reloadData];
}
You also need to define and use a different NSFetchedResultsController for each of your view controllers. This is because the data you want to fetch and display are, of course, different for each view controller (different entity, predicate etc). Simply define them in each implementation file. Here is an example:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Context" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
if(self.project){
// get the contexts for the project
NSPredicate * predicate = [NSPredicate predicateWithFormat: #"projectName == %#", self.project.name];
[request setPredicate:predicate];
}
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[request release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
and then use the fetchedResultsController as needed. For instance, put this in your viewDidLoad method:
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
I have two tableview controllers showing CoreData objects. One is a detail view (with sentences) one is an overview (with stories).
Pick a Story -> See the Sentences.
It looks like I was over-releasing my managedObjectContext; I originally released it in both TableViewControllers in dealloc and got a crash every third time I went between the two controllers (Story -> Sentence -> Story -> Sentence -> Story -> Crash).
Some debugging showed I was crashing in my App Delegate after this code in ViewDidLoad of both TableViewControllers:
if (managedObjectContext == nil)
{
managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}
Some more research found this discussion which led me to believe it was a case of an over released ManagedObjectContext:
The second more prosaic issue is simply an overreleased
NSManagedObject. Instruments ObjectAlloc tool should be able to help
you.
So I removed [managedObjectContext release]; from my dealloc in TableViewController and now I have no leaks (according to Instruments) and no crash.
It looks like the problem is fixed, but here's the question:
I might be missing the point altogether, and just hiding another issue. How can I find the over-release, or the real problem?
If I have fixed the problem, I'd like to know why it's fixed and why I don't need to release the MOC in the second TableViewController
MakeSentenceTableViewController.m
#implementation MakeSentenceTableViewController
#synthesize story, managedObjectContext;
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"My Story";
NSLog(#"Passed Story Object: %#", story);
if (managedObjectContext == nil)
{
NSLog(#"managedObjectContext == nil");
managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After managedObjectContext: %#", managedObjectContext);
}else{
NSLog(#"managedObjectContext != nil");
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sentence" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
//sorting stuff:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"order" ascending: YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
//[request setFetchBatchSize:FETCH_BATCH_SIZE];
[sortDescriptors release];
[sortDescriptor release];
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request managedObjectContext:managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
[request release];
NSError *error;
[fetchedResultsController performFetch:&error];
NSLog(#"FetchedResultsController: %#", fetchedResultsController);
NSLog(#"fetchedResultsController RetainCount at viewDidLoad: %d", [fetchedResultsController retainCount]);
}
//snip...table view bits
- (void)dealloc {
[fetchedResultsController release];
//Why don't I have to release this?
//[managedObjectContext release];
[super dealloc];
}
Because you are not retaining it. Even if the "MOC" property in you view controller is (retain), you are not calling the setter but simply setting the reference directly. If you want to retain and release it, you have to call self.managedObjectContext = ... instead (notice the dot), that is equivalent to [self setManagedObjectContext:...] and only then you can safely release it in the dealloc. Actually, since the "MOC" is owned by and managed in the app delegate, I wouldn't even bother to retain it.