NSPredicate, executeFetchRequest crash - ios

I'm doing a Core Data tutorial and I keep getting a crash. It's a objc_exception_throw.
I create a method called loadTableData and call it in viewDidLoad
-(void)loadTableData{
NSManagedObjectContext *context = [[self appDelegate]managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Label" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"label like %#", [context objectWithID: self.labelID]];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
self.artistArray = [context executeFetchRequest:fetchRequest error:&error];
[self.tableView reloadData];
}
It gets stuck here
self.artistArray = [context executeFetchRequest:fetchRequest error:&error];
Commenting out the predicate alloc/init and setPredicate method call results in an app that doesn't crash, but doesn't do what I want.
See entities and relationships below.
In LabelViewController here is additional code to show how [context objectWithID: self.labelID] is set
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MLBLArtistViewController *artistViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"ArtistViewController"];
Label *label = [self.labelArray objectAtIndex:indexPath.row];
artistViewController.labelID = [label objectID];
[self.navigationController pushViewController:artistViewController animated:YES];
}

I'd use a CONTAINS instead:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"label CONTAINS[cd] %#", [context objectWithID: self.labelID]];
You can use a LIKE but I like the simplicity of the CONTAINS, see this quesiton:
NSPredicate that is the equivalent of SQL's LIKE

First of all, it seems that you want to fetch "Artist" objects, not "Label" objects:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Artist" inManagedObjectContext:context];
Next, LIKE or CONTAINS are for testings strings, you need a simple ==:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"label == %#", [context objectWithID:self.labelID]];
Remark: It would be simpler to pass the label object itself to the pushed view controller,
instead of [label objectID].

Related

Attempting to reordering UITableView using an NSFetchedResultsController

I am attempting to edit the order of my UITableView while using Core Data and an NSFetchedResultsController. As I understand, Core Data does not have a built in method for rearranging objects in a Core Data model.
The idea was to create an array, reorder my objects there, and then write that data back to my model.
NSFetchedResultsController
-(NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"List" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"listName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
moveRowAtIndexPath
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; {
NSMutableArray *toDoItems = [[self.fetchedResultsController fetchedObjects] mutableCopy];
NSManagedObject *managedObject = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];
[toDoItems removeObject:managedObject];
[toDoItems insertObject:managedObject atIndex:[destinationIndexPath row]];
int i = 0;
for (NSManagedObject *moc in toDoItems) {
[moc setValue:[NSNumber numberWithInt:i++] forKey:#"listName"];
}
[self.managedObjectContext save:nil];
}
When I hit my Edit button, I can rearrange the rows but as soon as I let the row go, my app crashes. Im not getting any kind of stack trace in the console when it crashes.
I set a breakpoint on exception and it seems to be crashing on this line
[moc setValue:[NSNumber numberWithInt:i++] forKey:#"listName"];
My key name is correct. But I now realize this is completely wrong in that I am trying to set this as a number and that shouldnt be the case.
Any suggestion or push in the right direction would be appreciated.
Either amend your code to set a string value for the listName, something like this:
[moc setValue:[NSString stringWithFormat:"%d",i++] forKey:#"listName"];
(but beware because by default this will sort as a string, so 11 comes before 2, etc).
So, better, add an integer attribute to your model, and use that to sort the fetched results controller.
Don't copy NSManagedObject. It's a managed object by core data. If you want a new one ask the context.

Filtering a UITableView from NSFetchedResultsController and Core Data with NSPredicate

I have a UITableView using NSFetchedResultsController to display a list of users.
I added a UISegmentedControl to switch between my full list of users and only my active users.
To get my list of users, I use fetchedResultsController :
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastname" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
if (self.barSegmentedControl.selectedSegmentIndex == 1) {
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"active == YES"]];
}
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
To perform an update of the tableview when clicking on the UISegmentedControl, I use segmentedControlIndexChanged :
-(IBAction) segmentedControlIndexChanged{
self.fetchedResultsController = nil;
[self fetchedResultsController];
[self.tableView reloadData];
}
But I'm not sure I'm doing this right.
Could you say me if this is the right way to filter a UITableView with NSFetchedResultsController ?
I also wanted to know if it is possible to filter a list with an animation ?
Exactly like in the iPhone Phone App when in the Recents Tab, it switch from all calls to missed calls ?
Thanks for your help.
Your approach will work fine. I too using the same thing without any issues.
Coming to animation you can do them in its delegate methods,
Check Apple's Documentation here.
self.fetchedResultsController = nil; is a reasonable way to invalidate a property.
But you should delete this weird [self fetchedResultsController]; line.
[self.tableView reloadData]; will call your delegate which should access fetchedResultsController property (if you're using _fetchedResultsController variable in your datasource methods, that would be an anti-pattern).
filter a list with an animation
Prepare a list of rows to hide and send
[self.tableView deleteRowsAtIndexPaths: ... withRowAnimation:...]
instead of reloadData.

NSFetchedResultController doesn't update UITableView when I change the FetchRequest without changing NSSortDescriptor

I have an UITableView and a UISegmentControl in my view controller.
The view controller has two segments. When the user clicks on a segment, I refine the data in the viewController, and it should update in the UITableView. This doesn't work.
The data is displayed using a NSFetchedResultsController, whoses datasource is a CoreData table.
They use the same NSSortDescriptor, but not the same predicate.
I observed that when I change the NSSortDescriptor, it works, so this objects is the root of the problem.
When the user clicks on a segment, I call this code:
self.fetchedResultsController = nil;
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
And it updates the NSFetchedResultsController this way:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
//get the segment index:
NSUInteger selectedState = self.chooseTableSegmentedControl.selectedSegmentIndex;
//Update the fetched result consequently:
NSManagedObjectContext *context = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:NO];
NSPredicate *predicate;
if (selectedState == 0) {
[fetchRequest setSortDescriptors:#[dateDescriptor]];
} else if (selectedState == 1) {
predicate = [NSPredicate predicateWithFormat:
#"postedByUser == 0"];
[fetchRequest setSortDescriptors:#[dateDescriptor]];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:7];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#"Root"];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Any idea? Thanks a lot!
After creating a new fetched results controller and calling performFetch, you have to reload the table view with reloadData. performFetch does not trigger the FRC delegate methods.
Update: A FRC cache must be deleted when the fetch request changes (using deleteCacheWithName:). But the cache is only useful in connection with a sectionNameKeyPath:, so you can set cacheName:nil as well to solve the problem.

Can't fetch number of cells in tableView

I have a main tableViewController, touch a button self.navigation will push addItem viewController, click save self.navigation with pop the add VC, then we're back on the main one, I successfully add and save, also fetch, but when it comes to fetching number of cells, number returns 0, heres the method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"No. of cells determined: %lu", (unsigned long)[secInfo numberOfObjects]);
return[secInfo numberOfObjects];
}
NSLog gives me 0, whats the problem, this gave me headache.
Here's the fetch request:
- (NSFetchedResultsController*) fetchedResultsController {
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Goal"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// setting _fethcedResultsController
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// setting _fetchedResultsController to self
_fetchedResultsController.delegate = self; // for the tableview updating thing
// Thats it
return _fetchedResultsController;
}
Please note that when I run a check for items, it's not nil:
// fetching all items to check how many of them exists
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName:#"Goal"
inManagedObjectContext:self.managedObjectContext]];
NSManagedObjectContext *moc = self.managedObjectContext;
NSError *fetchError = nil;
NSArray *goals = [moc executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"No. of goals: %lu", (unsigned long)[goals count]);
// end of check for items
I forgot to put this code of line:
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
in method
- (NSFetchedResultsController*) fetchedResultsController;
so before I did, the app always creates new fetchedResultsController and over-write the old one.

sort NSFetchedResultsController on IBAction

i want to sort my Data in my Tableview. I have UIButtons with IBAction to call the sort.
I have created a string which contains the sortKey. I am setting the the key and call the fetchedResultsController again, to sort the tableview.
Problem is, fetchedResultsController method is not called and the sorting doesnt work.
here is my code:
- (IBAction) actionSortCardColor:(id) sender {
XLog(#"");
sortString = #"colorOrder";
[self fetchedResultsController];
[self actionRemoveSortView:sender];
}
Here my fetchedResultsController method:
- (NSFetchedResultsController *)fetchedResultsController
{
[...]
NSPredicate *inboxPred = [NSPredicate predicateWithFormat:#"archived == 0"];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setPredicate:inboxPred];
XLog(#"sortString: %#", sortString);
if (sortString == nil) {
sortString = [[NSString alloc] initWithString:#"sortingOrder"];
}
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"setTitle" ascending:YES] autorelease];
NSSortDescriptor *sortDescriptor2 = [[[NSSortDescriptor alloc] initWithKey:sortString ascending:YES] autorelease];
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil] autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
[...]
}
Where is it that you let the table view know about these changes? You'll need to reload the data for your table view instance [tableView reloadData];
When you set the __fetchedResultsController to nil, it cannot update the UITable view with any changes because it isn't in scope.

Resources