Adam K
Adam K

Reputation: 67

UITableView UI Will Not Refresh to Data from New NSFetchedResultsController

I have a UITableView which is being filled from Core Data by a NSFetchedResultsController. Everything works great. However, I have just implemented a UISegmentedControl at the top of the UITableView, which I would like to sort the results. There are three segments: @"All", @"Boys", @"Girls". In viewDidLoad, I instantiate a NSDictionary with three NSFetchedResultsController. Each has the exact same fetch request with a different predicate.

        allFetchRequest.predicate = [NSPredicate predicateWithFormat:@"school.schoolID == %@", [NSNumber numberWithInt:[_schoolID intValue]]];
        boysFetchRequest.predicate = [NSPredicate predicateWithFormat:@"school.schoolID == %@ AND gender == %@", [NSNumber numberWithInt:[_schoolID intValue]], @"M"];
        girlsFetchRequest.predicate = [NSPredicate predicateWithFormat:@"school.schoolID == %@ AND gender == %@", [NSNumber numberWithInt:[_schoolID intValue]], @"F"];

When the UISegmentControl value is changed, I call a method which changes the view controller's "currentFetchedResultsController" instance variable to the corresponding NSFetchedResultsController for that segment, calls perform fetch, then calls reloadData on the tableView in the main thread.

- (void)showBoys
{
    self.currentFetchedResultsController = self.fetchResultsControllerDictionary[@"boys"];
    [self.currentFetchedResultsController performFetch:nil];
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
}

It all seems to work great, except that the UITableView never seems to update its UI. It seems to show the correct number of sections for all, boys, and girls, but the object at each index doesn't change. For example, let's say we have 11 people in Core Data. Five guys, six girls. The view loads with the "All" segment pre-selected, so all 11 people load into the UITableView. However, when I switch the segment to "Boys", the number of rows will drop to five, but the objects in those rows never change. The UITableView will continue to show the first five objects that were already in the table, even if some are girls (gender == "F" in Core Data).

I know that the fetch is working properly because I have set up a small test:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];
    NSLog(@"Username = %@ & Gender = %@", user.username, user.gender);
}

Now, when I select a row, it logs the correct username and gender that SHOULD be at that indexPath. However, the logged username is different than the one that appears in the UITableView at that row.

Table View Data Source Methods:

- (UserTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Home Ranked Cell";

    UserTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];

    NSString *name = user.name;
    if(!name || [name isEqualToString:@""])
        name = user.username;

    cell.name.text = name;

    UserTableViewCell *previousCell = nil;

    if(indexPath.row != 0)
        previousCell = (UserTableViewCell *)[self tableView:tableView  cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section]];

    NSInteger previousVotes = 0;

    if(previousCell)
        previousVotes = [previousCell.votes.text integerValue];

    if(!previousCell) {
        cell.rank.text = [NSString stringWithFormat:@"%i", 1];
    } else if(previousVotes == [user.votes integerValue]) {
        cell.rank.text = previousCell.rank.text;
    } else {
        cell.rank.text = [NSString stringWithFormat:@"%i", [previousCell.rank.text integerValue] + 1];
    }

    if(user.profilePicture && user.profilePicture.thumbnailData && ![user.profilePicture.thumbnailData isEqualToString:@""]) {
        NSData *imageData = [[NSData alloc] initWithBase64EncodedString:user.profilePicture.thumbnailData options:0];
        cell.imageView.image = [UIImage imageWithData:imageData];
    }

    if(!cell.imageView.image)
        cell.imageView.image = [UIImage imageNamed:@"xIcon.png"];

    cell.votes.text = [NSString stringWithFormat:@"%i", [user.votes integerValue]];
    cell.upButton.tag = indexPath.row;
    cell.downButton.tag = indexPath.row;

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];
    NSLog(@"Username = %@ & Gender = %@", user.username, user.gender);
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView  
{
    return [self.currentFetchedResultsController sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    return [self.currentFetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    return self.segmentControl;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.currentFetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id sectionInfo = [[self.currentFetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

I have tried nearly everything. I have gone through every related thread and nothing has worked. What am I doing wrong?

** Newest Findings: When a cell is selected, the cell does not become highlighted (or show any UI change for that matter), unless it is the correct cell for that specific index path. For example, let's say Sally is in row 0 for all Users, and Tom is in row 1. If I switch the UISegmentedControl to "Male" and tap the first cell (row 0, which currently shows Sally), there is absolutely no UI indication that the cell has been tapped, although tableView: didSelectRowAtIndexPath still gets called, logging the cell information that belongs there (Tom's User info, since he belongs in row 0 of the "Male" Users).

Upvotes: 0

Views: 999

Answers (2)

Adam K
Adam K

Reputation: 67

It turns out I made a silly mistake when initializing my UITableViewCell. Instead of calling

 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

I was calling

 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier **forIndexPath:indexPath**];

The latter returns the current UITableViewCell dequeued from the indexPath mentioned. For some reason, if the cell is dequeued from an existing cell, the properties on the cell are readonly, causing all of my changes to be simply ignored. I hope this helps someone in the future, as I did not see anything about this on Stack Overflow, I just happened to stumble upon my mistake after hours of analyzing each line of code.

Upvotes: 0

Mundi
Mundi

Reputation: 80265

It seems to me that it would be easier to put the segmentation logic into the fetched results controller method. When switching the segmented control, just set your FRC to nil and account for the proper filter in the FRC creation code. You do not need 3 FRCs. Thus:

-(void)segmentedControlDidChange:(UISegmentedControl*)control {
   self.fetchedResultsController = nil;
   [self.tableView reloadData];
}

and when creating the FRC:

NSPredicate *basePredicate = [NSPredicate predicateWithFormat:
       @"school.schoolID = %@", _schoolID];
NSPredicate *secondPredicate = [NSPredicate predicateWithValue:YES];
NSInteger i = self.segmentedControl.selectedSegmentIndex;
if (i > 0) {
   secondPredicate = [NSPredicate predicateWithFormat:
     @"gender = %@", i == 1 ? @"M" : @"F"];
}
fetchRequest.predicate = [NSCompoundPredicate 
   andPredicateWithSubPredicates:@[basePredicate, secondPredicate]];

Upvotes: 2

Related Questions