Using NSFetchedResultsController to auto update UITableview but facing some issue. When trying to add/edit from another device the whole cell is vanished every time.
Here is the bunch of code
// NSFetchedResultsController Delegate methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.aTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
UITableView *tableView = self.aTableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
return;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.aTableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"9.0"))
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
else
[tableView reloadData];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.aTableView endUpdates];
}
// Fetch data from coredata
-(NSFetchedResultsController *)loadForumData {
if (_fetchedForumController != nil) {
return _fetchedForumController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SocialWall" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dateTime" ascending:NO];
[fetchRequest setSortDescriptors:[[NSArray alloc] initWithObjects:sortDescriptor, nil]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eventId == %#)",self.appDelegate.currentEvent.entityId];
[fetchRequest setPredicate:predicate];
fetchRequest.fetchLimit = _range+10;
fetchRequest.fetchOffset = 0; // start range
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
[theFetchedResultsController setDelegate:self];
_fetchedForumController = theFetchedResultsController;
[_fetchedForumController performFetch:nil];
return _fetchedForumController;
}
Here is the o/p what i'm getting on device/simulator after editing the cell . Please check the attached simulator screenshort - http://i.stack.imgur.com/LUMPh.png
Related
Problem with NSFetchedResultsController is that I fetched the data and it populates the UITableView with cacheName set to nil. Later when I change the predicate of NSFetchedResultsController and called perfromFetch, it won't refreshes the UITableView however the data inside NSFetchedResultsController is updated. One thing more, NSFetchedResultsControllerDelegate methods are also not invoking.
Thanks in Advance.
Edited: Added Code
NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )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;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSManagedObjectContext *context = [GmailDBService sharedService].managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Thread"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"historyId" ascending:NO];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(messages, $m, ANY $m.labels.identifier == %#).#count > 0", self.label.identifier];
fetchRequest.sortDescriptors = #[sortDescriptor];
fetchRequest.fetchBatchSize = 20;
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
[_fetchedResultsController performFetch:nil];
return _fetchedResultsController;
}
Predicate Refreshing
- (IBAction)unwindToThreadsController:(UIStoryboardSegue *)segue
{
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"identifier == %#", #"15f1919682399cc9"];
[self.fetchedResultsController performFetch:nil];
}
fetchedResultsController are not expensive. You should not be afraid to create and destroy them as needed. When you need to change the predicate discard the current fetchedResultsController and create a new one. Don't forget to reload the tableView after you do so.
The changes aren't triggering the fetchedResultsController because you are monitoring threads, but your predicate is based on messages. So when a message is changed it does not trigger a change in the fetchedResultsController. The controller only monitors changes to one entity and does not expect changes to other entities to effect it. You can fix this in a few ways:
setup your fetchedResultsController to look at message and group them by threads, then only display every section (ie thread) as a row.
every time you change a message also change its thread (message.thread.threadId = message.thread.threadId will count as a change as far as the fetchedResultsController is concerned).
Also fetchBatchSize isn't respected by a fetchedResultsController so you should remove it for clarity.
When I make some changes in another top layer controller with own context then I post notification to UITableViewController class for update cells. This notification calls performFetch after setting fetchedResultsController to nil. But I have seen this changes only if I reload UITableViewController entirely. I'm not using cache.
How to refresh tableView cells immediately after updating my PersistentStore?
- (void)performFetch {
NSError *error = nil;
_fetchedResultsController = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unable to perform fetch. Reason: %#", [error localizedDescription]);
}
[self.tableView reloadData];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
} else {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"orderingValue" ascending:YES];
[request setSortDescriptors:#[sort]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"syncStatus != %d", ObjectDeleted];
[request setEntity:entity];
[request setFetchBatchSize:20];
[request setPredicate:predicate];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController = fetchedResultsController;
_fetchedResultsController.delegate = self;
}
return _fetchedResultsController;
}
UPDATED:
This is FetchedResultsController Delegate method:
- (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:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(ItemCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
break;
}
}
Context of UITableViewController:
self.managedObjectContext = [[CoreDataController sharedInstance] newManagedObjectContext];
Context of DetailsViewController:
self.managedObjectContext = [[CoreDataController sharedInstance] masterManagedObjectContext];
CoreDataController:
- (NSManagedObjectContext *)masterManagedObjectContext {
if (_masterManagedObjectContext != nil) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_masterManagedObjectContext performBlockAndWait:^{
[_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];
}
return _masterManagedObjectContext;
}
- (NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext != nil) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setParentContext:masterContext];
}];
}
return newContext;
}
I am not sure what your complete setup involves but you do need to implement NSFetchedResultsControllerDelegate protocol methods to dynamically update your tableview cells. https://developer.apple.com/library/prerelease/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html
If you are using different managed object context to make updates, you will have to merge those changes as well before they are reflected in your context.
Update1
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:nil
usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = self.managedObjectContext;
if (note.object != moc) {
[moc performBlock:^(){
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
Update2
You can use the following template for NSFetchedResultsControllerDelegate
#pragma mark - NSFetchedResultsControllerDelegate
- (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;
default:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
I am updating a download view/button on a cell, and when I go to update my cell, I am not getting the correct section.
My code to get the index and update the download progress is this:
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
MyTableViewCell *cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:index];
DownloadProgressButtonView *buttonView = (DownloadProgressButtonView *)cell.accessoryView;
NSNumber *progressLong = [notification.userInfo objectForKey:#"progress"];
float progress = [progressLong floatValue];
NSNumber *totalBytesLong = [notification.userInfo objectForKey:#"totalBytes"];
float totalBytes = [totalBytesLong floatValue];
buttonView.progress = progress *.01;
float totalDownloadEstimate = totalBytes / 1.0e6;
float megaBytesDownloaded = (progress *.01) * totalDownloadEstimate;
cell.bottomLabel.text = [NSString stringWithFormat:#"%.1f MB of %.1f MB", megaBytesDownloaded, totalDownloadEstimate];
If I have two objects, each in a different section, they have the same row (0). When I go to update my cell, it updates the cell in section 1 instead of section 0. How do I fix this?
I can put whatever other code is needed. It works perfectly if I just disable sections in my NSFetchedResultsController.
My NSFetchedResultsController and delegates.
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *nameString = [[NSSortDescriptor alloc] initWithKey:self.sectionSortDescriptor ascending:NO];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:self.sortDescriptor ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:nameString,descriptor, nil]];
NSString *downloadStartedString = #"Preparing to download";
NSString *downloadingString = #"Downloading";
NSString *downloadPausedString = #"Download paused";
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"(downloaded == YES) OR (downloadStatus like[cd] %#) OR (downloadStatus like[cd] %#) OR (downloadStatus like[cd]%#)",downloadPausedString, downloadStartedString,downloadingString];
[fetchRequest setFetchBatchSize:20];
_fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:self.sectionNameString
cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
return _fetchedResultsController;
}
/*
NSFetchedResultsController delegate methods to respond to additions, removals and so on.
*/
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (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:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(StudioTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (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:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeMove:
NSLog(#"A table item was moved");
break;
case NSFetchedResultsChangeUpdate:
NSLog(#"A table item was updated");
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];
}
Finally when the download status changes, I update the object and send a notification to update the cell with the new status:
- (void)updateCell:(NSNotification *)notification
{
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
[self.tableView reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationFade];
}
Updating cells this way is not reliable. A cell updated in such a way will sooner or later be reused. The cell's subviews will be re-configured by tableView:cellForRowAtIndexPath:, based on the data provided by the datasource.
You should make changes to the Object itself (instead of passing them in notification's userInfo) and save the managed object context. Then NSFetchedResultsControllerDelegate callbacks will fire, allowing you to reload the corresponding row. Then you should set all the properties of MyTableViewCell in configureCell:atIndexPath.
And the configureCell: method should be called from cellForRowAtIndexPath method, not from the fetched results controller delegate method. The general pattern is to call reloadRowsAtIndexPaths: in controllerDidChangeObject:. Otherwise you can run into some cell reuse issues.
An idea on how the code should look like:
- (void)updateCell:(NSNotification *)notification
{
//depending on your Core Data contexts setup,
// you may need embed the code below in performBlock: on object's context,
// I omitted it for clarity
Object *obj = (Object *)notification.object;
//save changes to the object, for example:
NSNumber *progressLong = [notification.userInfo objectForKey:#"progress"];
obj.progress = progressLong;
//set all the properties you will need in configureCell:, then save context
[obj.magagedObjectContext save:&someError];
}
then the fetched results controller will call controllerDidChangeObject:, in this method you should reload the row:
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
finally, configure the cell (let's assume that you call configureCell:atIndexPath from tableView:cellForRowAtIndexPath:):
- (void)configureCell:(MyTableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath {
Object *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
DownloadProgressButtonView *buttonView = (DownloadProgressButtonView*) cell.accessoryView;
buttonView.progress = object.progress.floatValue *.01;
//and so on
}
I set up CoreData without NSFetchedResultsController and had everything saving fine. After switching to NSFetchedResultsController, I am getting a strange error when trying to save the image.
Here is the code I am using to save the image:
- (void)saveImage {
NSManagedObjectContext *context = [self managedObjectContext];
TimeTravelFeed *timeTravelFeed = [NSEntityDescription insertNewObjectForEntityForName:#"TimeTravelFeed" inManagedObjectContext:context];
NSData *imageData = UIImageJPEGRepresentation(self.thumbImage, 0.8f);
[timeTravelFeed setValue:imageData forKey:#"imageData"];
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//[tableView reloadData];
}
And here is the error message:
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40
2013-12-08 10:09:49.442 Time Travel[830:60b] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40 with userInfo (null)
2013-12-08 10:09:49.443 Time Travel[830:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40'
Here is the code for NSFetchedResultsController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailsViewController = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailsViewController animated:YES];
}
- (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:[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;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"TimeTravelFeed" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
The problem seems to be here:
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
You cannot sort on a "Binary Data" attribute. A fetched results controller
needs a sort descriptor, so you should use a different attribute, e.g. a string, number or date. For example
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"creationDate" ascending:NO];
where "creationDate" is an attribute (type "Date") and set when the object is created:
[timeTravelFeed setValue:imageData forKey:#"imageData"];
[timeTravelFeed setValue:[NSDate date] forKey:#"creationDate"];
Go to your DBfile, select entity named "TimeTravelFeed" and select key "imageData". Set properties.
I have been trying for several days to transfer my data base information (core data) to a tableview using fecthedResultsController but nothing seams to work!
my entity name is "password" and ther are 4 strings attributes.
i'm trying to make a table that its sections are defined by an attribute called "type".
right now, that is how my code on my mainViewController.m looks like:
-(NSFetchedResultsController*)fecthedResultsController
{
if(fecthedResultsController!=nil)
return fecthedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Password" inManagedObjectContext:[self manageObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"type" ascending:YES];
//here is the problam
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
fecthedResultsController=[[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[self manageObjectContext] sectionNameKeyPath:#"type" cacheName:nil];
fecthedResultsController.delegate=self;
NSLog(#"fecthedResultsController");
return fecthedResultsController;
}
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tblMain beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tblMain endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView*tableview=self.tblMain;
NSLog(#"didChangeObject");
switch (type) {
case NSFetchedResultsChangeInsert:
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:[tableview deleteRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:{
Password*p=[self.fecthedResultsController objectAtIndexPath:indexPath];
UITableViewCell*cell=[tableview cellForRowAtIndexPath:indexPath];
cell.textLabel.text=p.userName;
cell.detailTextLabel.text=p.password;
// cell.imageView.image=[UIImage imageWithData: p.photodata];
}
break;
case NSFetchedResultsChangeMove:
[tableview deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
default:
break; }
}
-(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger) sectionIndex forChangeType: (NSFetchedResultsChangeType)type
{
NSLog(#"didChangeSection");
switch (type) {
case NSFetchedResultsChangeInsert:
[self.tblMain insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:[self.tblMain deleteSections:[NSIndexSet
indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
default:
break;
}
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[self.fecthedResultsController sections]count];
// NSLog(#"%i",[[self.fecthedResultsController sections]count]);
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id<NSFetchedResultsSectionInfo>secInfo=[[self.fecthedResultsController sections]objectAtIndex:section];
return [secInfo numberOfObjects];
NSLog(#"numberOfRowsInSection");
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
Password*p=[self.fecthedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text=p.userName;
// if(p.photodata!=nil)
// cell.imageView.image=[UIImage imageWithData: p.photodata];
return cell;
}
-(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:
(NSInteger)section
{
return [[[self.fecthedResultsController sections]objectAtIndex:section]name];
NSLog(#"cellForRowAtIndexPath");
}
the error that the log gives me is:
2013-04-30 12:31:04.326 passwordCore[33586:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Password''
*** First throw call stack: (0x1fac012 0x13e9e7e 0x10eff57 0x2d18 0x3832 0x20383e 0x204554 0xba793 0xc9937 0xc92dc 0xccdd6 0xd1a7e 0x6e2dd 0x13fd6b0 0x25a8fc0 0x259d33c 0x259d150 0x251b0bc 0x251c227 0x25beb50 0x1c39f 0x1ce81 0x2dcb5 0x2ebeb 0x20698 0x1f07df9 0x1f07ad0 0x1f21bf5 0x1f21962 0x1f52bb6 0x1f51f44 0x1f51e1b 0x1c17a 0x1dffc 0x1d3d 0x1c65) libc++abi.dylib: terminate called throwing an exception (lldb)
and it points to the 6th line of my code.
i dont understand if the problam is the manageObjectContext or the entityForName:#"Password"
i was told that this is a esay way to deal with tableview and data..
Right now I'm just frustrated...
i will love for some help.
You should get hold of a valid, main thread based NSManagedObjectContext. in a default core data project (the one provided by the Apple template) the main managed object context is accessible via the AppDelegate.
You should either pass your context from view controller to view controller, or just ask for it from the AppDelegate like so:
self.managedObjectContext = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;