James V
James V

Reputation: 189

Applying a filter to NSFetchedResultsController

The following works to display the list of "Pins" on a table and I can change _category using an actionsheet successfully.

I want to only show results that match the given category (i.e. Travel), but still be able to add Pins that don't have the same category (i.e. Home) to managedObjectContext.

I have tried many ways to use a predicate to filter the fetched data (by comparing _category to the 'category' property of Pin) to no avail - the furthest I've gotten still showed all of my entries regardless of category but only allowed pins of the chosen category to be added.

Most of this code is from the CoreData/TableView template provided with Xcode.

Any help would be incredibly helpful - this is my first time using CoreData.

- (void)setFilter {
//load
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *title = [prefs stringForKey:@"Filter"];
_category = title;

if ([title isEqualToString:@"None"] || title.length < 1) {
    title = [NSString stringWithFormat:@"All Pins"];
}else{
    title = [NSString stringWithFormat:@"%@ Pins", title];
}
self.navigationItem.title = title;

[self.tableView reloadData];
}

///////////////////////////////////////////////////////////////////////////
//
//                            Action Sheet
//
///////////////////////////////////////////////////////////////////////////

- (IBAction)showCategorySheet:(id)sender {
UIActionSheet *popupQuery;
popupQuery = [[UIActionSheet alloc] initWithTitle:@"Category" delegate:self cancelButtonTitle:@"All" destructiveButtonTitle:nil
                                otherButtonTitles:@"Family", @"Friends", @"Home", @"Work", @"Travel", nil];
popupQuery.actionSheetStyle = UIActionSheetStyleDefault;
[popupQuery showInView:self.view];
}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {

NSString *filter;

if (buttonIndex == 0) {
    //Family
    filter = @"Family";
}else if (buttonIndex == 1) {
    //Friends
    filter = @"Friends";
}else if (buttonIndex == 2) {
    //Home
    filter = @"Home";
}else if (buttonIndex == 3) {
    //Work
    filter = @"Work";
}else if (buttonIndex == 4) {
    //Travel
    filter = @"Travel";
}else if (buttonIndex == 5) {
    //None
    filter = @"None";
}
//save
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:filter forKey:@"Filter"];
[self setFilter];
}

///////////////////////////////////////////////////////////////////////////
//
//                               Table
//
///////////////////////////////////////////////////////////////////////////

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60.0;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController 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 = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

    NSError *error = nil;
    if (![context save:&error]) {
         // Replace this implementation with code to handle the error appropriately.
         // abort() causes the application to generate a crash log and terminate.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}   
}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// The table view should not be re-orderable.
return NO;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    self.detailViewController.detailItem = object;
}
}

///////////////////////////////////////////////////////////////////////////
//
//                           Data Handling
//
///////////////////////////////////////////////////////////////////////////

- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
    return _fetchedResultsController;
}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Pin" 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:@"creationDate" ascending:NO];
NSArray *sortDescriptors = @[sortDescriptor];

[fetchRequest 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:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
     // Replace this implementation with code to handle the error appropriately.
     // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

return _fetchedResultsController;
}    

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
       atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
    case NSFetchedResultsChangeInsert:
        [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
        break;
}
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;

switch(type) {
    case NSFetchedResultsChangeInsert:
        [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        break;

    case NSFetchedResultsChangeMove:
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;
}
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(@"object: %@", [object valueForKey:@"category"]);
NSLog(@"filter: %@", _category);
//time
NSDate *now = [object valueForKey:@"creationDate"];
NSString *formattedTime = [NSString stringWithFormat:@"%@", [self formattedDate:now type:@"date"]];

cell.textLabel.text = [[object valueForKey:@"pinName"] description];
cell.detailTextLabel.text = formattedTime;

if ([[object valueForKey:@"pinPictureThumb"] description].length > 0) {
    NSData *data = [object valueForKey:@"pinPictureThumb"];
    UIImage *img = [[UIImage alloc] initWithData:data];
    cell.imageView.image = img;
}else{
    cell.imageView.image = nil;
}
}

Upvotes: 1

Views: 1740

Answers (1)

JiuJitsuCoder
JiuJitsuCoder

Reputation: 1876

In your fetchedResultsController method you need to add an NSPredicate* object to your fetch request right after your [fetchRequest setSortDescriptors:sortDescriptors]; line and then run your fetch request normally.

NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"category=%@", categoryToFind];
[fetchRequest setPredicate:fetchPredicate];

The format I show above is copied from some code of mine, so it may not be the EXACT predicate format you are looking for. If you are having trouble formatting the predicate correctly, check out the apple docs here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html

There are also a bunch of threads on SO about formatting predicates.

Upvotes: 4

Related Questions