FetchedResultsController(FRC) – Part 2

In the previous post we saw what is fetched results controller and its usage of single fetchedResultsControllers in a same view controller. In this post we will see the usage multiple fetched results controller in same view controller.

Using multiple fetchedResultsControllers in same view controller:

You may rise a question wether can we use multiple FRC in same view controller?

  • Yes, we can. Let us take a scenario where we can use two FRCs in a single view controller.
  • Think about a TODO application in which we need to list two sections in a tableView where first section will list the todo item which are un-completed from “UnCompleted” table, and the second section will list the completed todo list from “Completed” table.

Create two instances of NSFetchedResultsController.

//CODE
//FRC instances
@property (strong, nonatomic) NSFetchedResultsController *completedFetchedResultsController;
@property (strong, nonatomic) NSFetchedResultsController *unCompletedFetchedResultsController;

– (void)viewDidLoad {
          [super viewDidLoad];

          //Initialize fetchedResultsController
          // Perform Fetch action
          NSError *completedError = nil;
          [self.completedFetchedResultsController performFetch:&completedError];

          // Handle error
          if (completedError) {
          // Handle error here. (eg., alert, logging, retry option,…)
          }

          // Perform Fetch action
          NSError *unCompletedError = nil;
          [self.unCompletedFetchedResultsController performFetch:&unCompletedError];

          // Handle error
          if (unCompletedError) {
          // Handle error here. (eg., alert, logging, retry option,…)
          }
}

//Initialize the two fetchedResultsController
– (NSFetchedResultsController *)completedFetchedResultsController {
          //Return FRC if its already fetched
          if (self.completedFetchedResultsController!= nil) {
                    return self.completedFetchedResultsController;
          }

          // Initialize Fetch Request
          NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@”Completed”];

          // Add sort descriptors(Mandatory)
          [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@”updatedAt” ascending:YES]]];

          // Add batchSize(Optional)
          [fetchRequest setFetchBatchSize:20];

          // Initialize FetchedResultsController
          self.completedFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];

          // Assign delegate for FetchedResultsController
          [self.completedFetchedResultsController setDelegate:self];

          // Return fetched result controller
          return self.completedFetchedResultsController;
}

– (NSFetchedResultsController *)unCompletedFetchedResultsController {
          //Return FRC if its already fetched
          if (self.unCompletedFetchedResultsController != nil) {
                    return self.unCompletedFetchedResultsController;
          }

          // Initialize Fetch Request
          NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@”UnCompleted”];

          // Add sort descriptors(Mandatory)
          [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@”updatedAt” ascending:YES]]];

          // Add batchSize(Optional)
          [fetchRequest setFetchBatchSize:20];

          // Initialize FetchedResultsController
          self.unCompletedFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];

          // Assign delegate for FetchedResultsController
          [self.unCompletedFetchedResultsController setDelegate:self];

          //Return fetched result controller
          return self.unCompletedFetchedResultsController;
}

  • Now we have initialized two fetchedResultsControllers and has required data in the same view controller. Now we have to be careful while setting tableView delegate and datasource.
  • Lets now configure tableView datasource and delegate methods by making use of the two fetchedResultsControllers we have,

Implementing : UITableViewDataSource using two FRCs:- 

//Set number of sections
– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
          return [[self.completedFetchedResultsController sections] count] + [[self.unCompletedFetchedResultsController sections] count];
}

//Set number of rows in each section
– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
          int rowsCount = 0;

          if (section < [[self.completedFetchedResultsController sections] count] ) {         //First FRC
                    id<NSFetchedResultsSectionInfo>  sectionInfo = [self.completedFetchedResultsController sections][section];
                    rowsCount = [sectionInfo numberOfObjects];
          } else {    //Second FRC
                    id<NSFetchedResultsSectionInfo>  sectionInfo = [self.unCompletedFetchedResultsController sections][section];
                    rowsCount = [sectionInfo numberOfObjects];
          }

          return rowsCount;
}

//Now configuring cell for row at index path, mostly there wont be any change in this datasource since the object received from “configureCell:atIndexPath” returns NSManagedObject there wont be any changes needed in cellForRowAtIndexPath.
– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:@”Cell” forIndexPath:indexPath];

          // Configure tableViewCell using fetchedResultController
          [self configureCell:cell atIndexPath:indexPath];
          return cell;
}

//In configureCell:atIndexPath we need to return object based on the section in our case as shown below.
– (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {

          if (indexPath.section == 1) {
                    // Get respective record from fetchedResultController
                    //Instances for “Completed”(Subclass of NSManagedObject class) table
                    Completed *completedObject *record = [self.completedFetchedResultsController objectAtIndexPath:indexPath];

                    // Update cell contents
                    [cell.titleLabel setText:[record valueForKey:@“note”]];
          } else if (indexPath.section == 2) {
                    // Get respective record from fetchedResultController
                    //Instances for “UnCompleted”(Subclass of NSManagedObject class) table
                    UnCompleted *record = [self.unCompletedFetchedResultsController objectAtIndexPath:indexPath];

                    // Update cell contents
                    [cell.titleLabel setText:[record valueForKey:@“note”]];
}

Thats it, this is a sample shown above which shows how to handle multiple fetched results controller in a same view controller, but we can use multiple fetched results controller in many other cases based on your requirement.

Some advanced topics in FetchedResultsController:

  • mergeChangesFromContextDidSaveNotification:
    It merges the changes specified in a given notification. This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted. You can pass a NSManagedObjectContextDidSaveNotification notification posted by a managed object context on another thread, however you must not use the managed objects in the user info dictionary directly. For more details, see Concurrency with Core Data.
Bharath R,
iOS Developer,
Mallow Technologies.

Leave a Reply

%d bloggers like this: