I have a simple application that is a two tabbed UITabBarController
. The first Tab is called Languages
and it contains a list of hard coded Languages in the app. In the prepareForSegue
method, I am populating a list of Leaflets and Videos for that language by NSMutableArray
and passing that successfully over to a new UITableViewController
called LeafletsAndVideos
. Some languages have Leaflets and Videos and some have just Leaflets, where Leaflets are in Section 0 and Videos are in Section 1.
With reference to this question: Changing the UIImage of a specific UITableViewCell when an event occurs, I have set up the use of "Favourting" specific cells in the LeafetsAndVideos
. Through the use of Core Data
, I am taking the title of the cell that was favourited, adding it to the Core Data
model. The reason I do this is because the second tab is called Favourites
and through the use of NSFetchedResultsController
, I am populating this UITableView
with any cells that have been "Favourited".
That park works really well.
The problem now is that when in the LeafletsAndVideos
UITableView
, I favourite a cell and it appears in the Favourites
tab straight away through the use of NSFRC on the FavouritesTableViewController
, but if I want to unfavorite the same cell, I want that to be deleted from the Favourites
tab.
Issue
The issue is that when I unfavorite a cell, it is just added to the Favourites
tab again.
For the sake of clarity, when I say "Favourite", let's say I am tapping on the cell (didSelectCell
) and so when I tap on it again, it unfavorites.
The LeafletsAndTable
UITableView
has no reference to the NSFRC
and it doesn't really need one because the cells are being populated by the NSMutableArray's
set in the Languages UITableView
(I totally understand I can just use Core Data
for the titles, but that's complicated in this particular setup - Core Data
is just being used as a mechanism to detect the titles of the Favourited cells).
Within my LeafletAndVideo
UITableView
, I have the following code when setting up the Favourites
:
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
CustomLeafletVideoTableViewCell *cell = (CustomLeafletVideoTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
NSString *cellTitle = cell.customCellLabel.text;
NSManagedObjectContext *context = [self managedObjectContext];
Favourites *favourites = [NSEntityDescription insertNewObjectForEntityForName:@"Favourites" inManagedObjectContext:context];
favourites.title = cellTitle;
NSString *key = [NSString stringWithFormat:@"%@_%ld_%ld", self.selectedLanguage, (long)indexPath.section, (long)indexPath.row];
if (self.favoritesDict[key] == nil) {
self.favoritesDict[key] = @(1);
} else {
[self.favoritesDict removeObjectForKey:key];
}
[[NSUserDefaults standardUserDefaults] setObject:self.favoritesDict forKey:@"favoritesDict"];
NSError *error = nil;
if (![context save:&error])
{
// Error
}
[cell hideUtilityButtonsAnimated:YES];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Issue
With the code above, when unfavouriting a cell, it gets added again to the Favourites tab (which is fair enough because I'm not doing any deleting here).
So in the else statement of that code, I tried something. I remove the object from the dictionary but I am also calling the Favourites
tab and the method completeFetchedResults
:
if (self.favoritesDict[key] == nil) {
self.favoritesDict[key] = @(1);
} else {
[self.favoritesDict removeObjectForKey:key];
FavouritesTableViewController *favTab = [[FavouritesTableViewController alloc] init];
[favTab completeFetchedResults];
[favTab moreButtonPressed:indexPath];
}
This method is simply calling the fetch on the NSFRC
.
- (void)completeFetchedResults
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
{
//exit(-1);
}
}
I am then calling the method to delete the object by passing it the indexPath
:
- (void)moreButtonPressed:(NSIndexPath *)indexPath
{
NSManagedObjectContext *context = [self managedObjectContext];
[self.managedObjectContext deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error = nil;
if (![context save:&error])
{
// Error
}
}
When I run this, select a cell, it favourites it (as expected) and gets added to the Favourites
tab. When I select the cell again (to remove the favourite), it crashes out with this error:
'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
When I place an exception breakpoint, it crashes at:
if (![context save:&error])
{
// Error
}
In the moreButtonPressed
method.
I'm not too sure how to fix this, but the desired effect is that when I select the cell, it favourites it (working) and when I select the cell again, it:
1) Deletes the object from the Favourites tab (not working)
Any guidance would really be appreciated.
Stack Trace Update
0 CoreFoundation 0x000000010c588f45 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010bc7adeb objc_exception_throw + 48
2 CoreFoundation 0x000000010c477b14 -[__NSArrayI objectAtIndex:] + 164
3 Street Parchar 0x000000010b5e8b37 -[FavouritesTableViewController moreButtonPressed:] + 151
4 Street Parchar 0x000000010b5f2386 -[LeafletsAndVideosTableViewController swipeableTableViewCell:didTriggerRightUtilityButtonWithIndex:] + 1158
5 Street Parchar 0x000000010b5e44d5 -[SWTableViewCell rightUtilityButtonHandler:] + 213
6 UIKit 0x000000010d1e194f _UIGestureRecognizerSendTargetActions + 153
7 UIKit 0x000000010d1ddfc1 _UIGestureRecognizerSendActions + 162
8 UIKit 0x000000010d1dbfbe -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 843
9 UIKit 0x000000010d1e4283 ___UIGestureRecognizerUpdate_block_invoke898 + 79
10 UIKit 0x000000010d1e4121 _UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks + 342
11 UIKit 0x000000010d1d1bdd _UIGestureRecognizerUpdate + 2634
12 UIKit 0x000000010cd6f9c0 -[UIWindow _sendGesturesForEvent:] + 1137
13 UIKit 0x000000010cd70bf6 -[UIWindow sendEvent:] + 849
14 UIKit 0x000000010cd202fa -[UIApplication sendEvent:] + 263
15 UIKit 0x000000010ccfaabf _UIApplicationHandleEventQueue + 6844
16 CoreFoundation 0x000000010c4b5011 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
17 CoreFoundation 0x000000010c4aaf3c __CFRunLoopDoSources0 + 556
18 CoreFoundation 0x000000010c4aa3f3 __CFRunLoopRun + 867
19 CoreFoundation 0x000000010c4a9e08 CFRunLoopRunSpecific + 488
20 GraphicsServices 0x0000000111165ad2 GSEventRunModal + 161
21 UIKit 0x000000010cd0030d UIApplicationMain + 171
22 Street Parchar 0x000000010b5dfedf main + 111
23 libdyld.dylib 0x00000001104ae92d start + 1
24 ??? 0x0000000000000001 0x0 + 1
) libc++abi.dylib: terminating with uncaught exception of type NSException
Update 2
Including the fetchedResultsController
in the FavouritesTab
- (NSFetchedResultsController *)fetchedResultsController
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Favourites" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSPredicate *d = [NSPredicate predicateWithFormat:@"title != nil"];
[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;
}
The problem is the new instance of FavouritesTableViewController
which has no connection to the corresponding object in Interface Builder.
You have to perform all Core Data related tasks in the LeafletsAndVideos
controller like this code
NSString *key = [NSString stringWithFormat:@"%@_%ld_%ld", self.selectedLanguage, (long)indexPath.section, (long)indexPath.row];
if (self.favoritesDict[key] == nil) {
self.favoritesDict[key] = @(1);
Favourites *favourites = [NSEntityDescription insertNewObjectForEntityForName:@"Favourites" inManagedObjectContext:context];
favourites.title = cellTitle;
}
else {
[self.favoritesDict removeObjectForKey:key];
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Favourites" inManagedObjectContext: context];
request.predicate = [NSPredicate predicateWithFormat:@"title == %@", cellTitle];
NSArray *items = [context executeFetchRequest:request error:&error];
if (error == nil && items.count) {
NSManagedObject *managedObject = items[0];
[context deleteObject:managedObject];
}
}
When you switch to the Favourites tab and perform a fetch in viewWillAppear
the data will be displayed correctly.