I am building up a very simple application, allowing users to browse leaflets and videos within the application on a particular topic. One of the features I'm bringing is being able to mark a leaflet or video as a favourite.
The application is UITabBar
with 5 tabs and every tab being represented by a UITableViewController
. When the user taps to hold on a cell in a tab, it marks it as "starred" and with the use of Core Data
and NSFetchedResultsController
, the idea is for that entry to appear in the Starred tab.
This is my simple Core Data model:
So when the user taps and holds a cell in any one of the 4 tabs, this is the code I run:
- (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index
{
switch (index) {
case 0:
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
CustomLeafletVideoTableViewCell *cell = (CustomLeafletVideoTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
NSString *cellTitle = cell.customCellLabel.text;
[self moreButtonPressed:cellTitle];
[cell hideUtilityButtonsAnimated:YES];
break;
}
default:
break;
}
}
- (void)cellPressed:(NSString *)passedString
{
NSManagedObjectContext *context = [self managedObjectContext];
Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:context];
Videos *videos = [NSEntityDescription insertNewObjectForEntityForName:@"Videos" inManagedObjectContext:context];
videos.title = passedString;
item.video = videos;
NSLog(@"Passed String = %@", videos.title);
}
I have created a FavouritesTableViewController
class and here's the main code:
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:@selector(managedObjectContext)])
{
context = [delegate managedObjectContext];
}
return context;
}
- (NSFetchedResultsController *)fetchedResultsController
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Videos" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSPredicate *d = [NSPredicate predicateWithFormat:@"items.video.@count !=0"];
[fetchRequest setPredicate:d];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
fetchRequest.fetchBatchSize = 20;
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
{
//exit(-1);
}
self.favouritesTableView.dataSource = self;
self.favouritesTableView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.favouritesTableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
#pragma mark Cell Configuration
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
CustomLeafletVideoTableViewCell *customCell = (CustomLeafletVideoTableViewCell *)cell;
Videos *videos = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(@"What is the video . title %@", videos.title);
customCell.customCellLabel.text = videos.title;
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"FavouritesCell";
CustomLeafletVideoTableViewCell *cell = (CustomLeafletVideoTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
#pragma mark NSFetchedResultsControllerDelegate Methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
// The boiler plate code for the NSFetchedResultsControllerDelegate
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
Issues
My issues seem to stem from the NSFetchedResultsController
. In that method, if I leave it as it is with the predicate, when I tap to hold the cell on the other tab, the app crashes with:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Videos 0x7ffb48d0b910> valueForUndefinedKey:]: the entity Videos is not key value coding-compliant for the key "@count".'
If I remove the predicate line:
// NSPredicate *d = [NSPredicate predicateWithFormat:@"items.video.@count !=0"];
// [fetchRequest setPredicate:d];
when I tap to hold a cell, it marks it as favourite and then when I go to the favourites tab, the entry is there. However, if I launch the app again, the entries in the Favourites tab have gone.
I'm not quite sure what's going on here. Essentially, the Favourites tab is a place for storing the starred items from the user from the other tabs. Do I need a predicate and if I don't, why is the data not persisting through each launch?
The app was set up with Core Data
selected, so the AppDelegate
has been set up appropriately.
Any guidance on this would be really appreciated.
The video
property of Item
is a to-one relationship. Basically it's a pointer to an object (or nil). You can't count it, it's not a collection of objects.
So your key path items.video.@count
and in particular the video.@count
doesn't make sense, hence the crash.
If you want to check if there is a video
for a given Item
, use @"items.video != nil"
.
Also you should probably follow conventions and use singular for your objects names (Leaflet
and Video
) and singular for to-one relationships (item
instead of items
).