How to get Apples buggy "Locations" example to work? - iphone

Unfortunately, Apple did not test it's tutorial at all. The "Locations" demo is really buggy and the edit Button does not even exist. I have no typo. First I didn't do copy & paste, and after that, I also attempted to just copy&paste their stuff. A lot of relevant code is completely missing.
They simply do this in view did load, without ever creating the edit button, anywhere:
- (void)viewDidLoad {
[super viewDidLoad];
// Set the title.
self.title = #"Locations";
// Configure the add and edit buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem; // WTF?
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addEvent)];
addButton.enabled = NO;
self.navigationItem.rightBarButtonItem = addButton;
// Start the location manager.
[[self locationManager] startUpdatingLocation];
/*
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 creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
// Set self's events array to the mutable array, then clean up.
[self setEventsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
}
I went through the whole tutorial and it works so far, except that I can't delete cells because they didn't get that edit button right here.
I tried to fix that myself with this line in -viewDidLoad:
// Setup the buttons for the navigation bar
editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editEvent)];
self.navigationItem.leftBarButtonItem = editButton;
but now, what's the implementation of the editEvent method has to look like? Unfortunately all this stuff is completely missing in the Core Data tutorial.

Apple's code is fine. Although editButtonItem is an instance method rather than a read-only property, the dot syntax still works (since dot syntax is just shorthand for method calls--usually accessors).

Related

Keep getting sigabrt error with core data

I'm learning about core data at the moment and working on a simple version of apple's core data tutorial (Locations).
Below is the code for the addEvent method:
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:managedObjectContext];
[event setCreationDate:[NSDate date]];
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
[eventsArray insertObject:event atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
When this method is triggered I am getting a sigabrt error. However I've found that if i include apple's viewDidLoad code the error doesn't occur anymore and I can't for the life of me figure out why?
Here's the code that stops the error occurring:
- (void)viewDidLoad {
[super viewDidLoad];
// Set the title.
self.title = #"Locations";
// Configure the add and edit buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addEvent)];
self.navigationItem.rightBarButtonItem = addButton;
/*
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 creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
// Set self's events array to the mutable array, then clean up.
[self setEventsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
What am i missing? I don't see how a a lack of a fetch request on viewDidLoad causes the error
Thanks
How does your tableview know which events to show?
One side effect of Apple's viewDidLoad method is that it will create the eventsArray. If you don't ever create that but you tell your tableView that you have inserted a row, you'd better have that row available!
You're calling
[eventsArray insertObject:event atIndex:0];
but I bet that without Apple's viewDidLoad method, eventsArray is nil - you will need to create it if it's nil (i.e. eventsArray = [[NSMutableArray alloc] init];
I've had this very same problem while working through Apple's tutorial.
Stepping through with the debugger it is falling over on the
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
line on the
-(void)addEvent
in the RootViewController.m
The reason that the code on the original posters stops the error from occuring is because the line the fires off the location manager is missing from the -(void)viewDidLoad.
[[self locationManager] startUpdatingLocation]
Thus when the
-(void)addEvent
is triggered the procedure is hitting the return and exiting
if (!location) {
return;
}
and is therefore not getting to the offending line, I added the startUpdatingLocation back to the viewDidLoad of the original posters code and the sigabrt error is back again.
I know this isn't an answer but I hope this saves someone else half an hour.

iOS Core Data - Design Pattern for Passing Context to Detail Views

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
}

Example of how to make a data factory for core data access in cocoa (iPhone)?

I have been slowly learning iPhone development and seem to keep hitting walls where I can't figure out how to do what I want to do the right way :(
Basically, I want a class that handles all interactions with the data layer, for example, getting a mutable array of some list of objects from the data store.
This is pretty trivial in other languages where you have a garbage collector, but in Objective-C on the iPhone, I'm not sure what to do.
This is an example method on a DataFactory class we were creating. Note the comment on where we are not sure when to release....
- (NSMutableArray*)fetchAllDrivers{
NSMutableArray *results = [[NSMutableArray alloc] init];;
if (self.appDelegate != nil) {
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error;
results = [[context executeFetchRequest:request error:&error] mutableCopy];
if (results == nil) {
//something went wrong
}
//Where should this be released??? Certainly not here!
[results release];
[request release];
}
else {
[NSException raise:#"Can't fetch b/c app delgate is nil!" format: #"!!!"];
}
return results;
}
Calling code, related to my comment:
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
Following naming conventions, your fetchAllDrivers should return an autoreleased object.
- (NSMutableArray*)fetchAllDrivers
{
if (!self.appDelegate) {
// Big Problems Raise exception immediately if you want...
return nil;
}
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error = nil;
NSMutableArray *results = [[NSMutableArray alloc] initWithArray:[context executeFetchRequest:request error:&error] copyItems:YES];
if (error) {
// Something went wrong
[results release];
// Error handling code here
[request release];
return nil;
}
[request release];
return [results autorelease];
}
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
By convention, any object returned from the method of an external object is autoreleased. You don't need to retain them except in properties. If you only using arr in the local scope of the method then you don't need to retain/release it. It is autoreleased and will die after the end of the local scope.
If you need to have arr hang around inside the object. You should store it in a retained property:
#property (nonatomic,retain) NSMutableArray *arr;
... then use it with the self notation to ensure retention:
self.arr=[dataFactory fetchAllDrivers];
... then you need only release it in the class' dealloc method.
Having one object manage your data model is very good idea but it is not a "factory". Objective-c does not use factories like C++ and similar languages. Trying to think in those terms will lead to grief. The object should instead be thought of as a "controller" or "manager".

data not reloading into tableview from core data on minor update

I've got a basic photo album application, on the first view a list of albums is displayed with a subtitle showing how many images are in each album. I've got everything working to add albums, and add images to albums.
The problem is that the image count lines are accurate whenever the app loads, but I can't get them to update during execution.
The following viewdidload correctly populates all lines of the tableview when the app loads:
- (void)viewDidLoad {
[super viewDidLoad];
// Set the title.
self.title = #"Photo albums";
// Configure the add and edit buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addAlbum)];
addButton.enabled = YES;
self.navigationItem.rightBarButtonItem = addButton;
/*
Fetch existing albums.
Create a fetch request; find the Album entity and assign it to the request; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Order the albums by name.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"albumName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
LocationsAppDelegate *mainDelegate = (LocationsAppDelegate *)[[UIApplication sharedApplication] delegate];
// Set master albums array to the mutable array, then clean up.
[mainDelegate setAlbumsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
}
But when I run similar code inside viewdidappear, nothing happens:
{
/*
Fetch existing albums.
Create a fetch request; find the Album entity and assign it to the request; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Order the albums by creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"albumName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
LocationsAppDelegate *mainDelegate = (LocationsAppDelegate *)[[UIApplication sharedApplication] delegate];
// Set master albums array to the mutable array, then clean up.
[mainDelegate setAlbumsArray:mutableFetchResults];
[self.tableView reloadData];
[mutableFetchResults release];
[request release];
}
Apologies if I've missed the answer to this question elsewhere, but what am I missing?
To troubleshoot, put NSLog statements in a few places in -viewDidAppear:. You would use these log statements to make sure that this method is both being called properly and to examine the contents of mutableFetchResults, at least.

Core Data combined Query

Hey, i have question related to CoreData. My iphone project has 2 Entities, Organisation and Brand with a 1 to many "BrandsToOrg" relationship and inverse.
So my project has a Mapview, where you can see all the Organisations and a little subview when you click on those Organisations.At the subview there is a "show Brands" Button, which should init a new TableView who only shows the brands belong to the seleceted organisation.
Any ideas how i can code that?
thx
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"(TitleMedium == %#)",#"Rock Antenne"];????
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
NSString *sortKey=#"TitleMedium";
BOOL sortAscending=YES;
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[request release];
[self setEntityArray:mutableFetchResults];
Here's an example of how you would do this with a top-level UITableView of orgs and a detailed table view of brands. At the top level, you do a fetch request for the orgs and display them. When a particular one is selected, you create the detail view of brands. The BrandsViewController object has a "org" property that tells is what to display; it can then refer to "org.brands" (which is the Core Data one-to-many relationship) to locate all the brands for that display. Note that you don't need to do an explicit Core Data fetch within the BrandsViewController to get the brands.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
BrandsViewController *targetViewController = [[BrandsViewController alloc]
initWithNibName:#"BrandsView" bundle:nil];
// pass managedObjectContext along to new view controller
targetViewController.managedObjectContext = self.managedObjectContext;
// Pass the selected org to the new view controller
targetViewController.org = (Organisation *)[fetchedResultsController objectAtIndexPath:indexPath];
[[self navigationController] pushViewController:targetViewController animated:YES];
[targetViewController release];
}