Can't fetch number of cells in tableView - ios

I have a main tableViewController, touch a button self.navigation will push addItem viewController, click save self.navigation with pop the add VC, then we're back on the main one, I successfully add and save, also fetch, but when it comes to fetching number of cells, number returns 0, heres the method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"No. of cells determined: %lu", (unsigned long)[secInfo numberOfObjects]);
return[secInfo numberOfObjects];
}
NSLog gives me 0, whats the problem, this gave me headache.
Here's the fetch request:
- (NSFetchedResultsController*) fetchedResultsController {
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Goal"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// setting _fethcedResultsController
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// setting _fetchedResultsController to self
_fetchedResultsController.delegate = self; // for the tableview updating thing
// Thats it
return _fetchedResultsController;
}
Please note that when I run a check for items, it's not nil:
// fetching all items to check how many of them exists
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName:#"Goal"
inManagedObjectContext:self.managedObjectContext]];
NSManagedObjectContext *moc = self.managedObjectContext;
NSError *fetchError = nil;
NSArray *goals = [moc executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"No. of goals: %lu", (unsigned long)[goals count]);
// end of check for items

I forgot to put this code of line:
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
in method
- (NSFetchedResultsController*) fetchedResultsController;
so before I did, the app always creates new fetchedResultsController and over-write the old one.

Related

Reload fetchedResultsController data

I have table view managed by fetched results controller.
What i want is, to change sort order when user tap segmented control.
This is how i create fetched results controller:
- (NSFetchedResultsController *)frc {
if (_frc != nil) {
return _frc;
}
NSString *sortCase;
switch (self.sortOrder) {
case tableSortServer:
sortCase = [NSString stringWithFormat:#"%#", CD_SORT];
break;
case tableSortDate:
sortCase = [NSString stringWithFormat:#"%#", CD_DATE];
break;
default:
sortCase = [NSString stringWithFormat:#"%#", CD_SORT];
break;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:CD_ENTITY_NAME inManagedObjectContext:self.getManagedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:sortCase ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:BATCH_SIZE];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.getManagedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.frc = theFetchedResultsController;
_frc.delegate = self;
return _frc;
}
In segmented controller callback method i did:
- (IBAction)sortOrderChanged:(id)sender {
UISegmentedControl *sc = sender;
if (sc.selectedSegmentIndex == 0){
self.sortOrder = tableSortServer;
[self.tableView reloadData];
} else {
self.sortOrder = tableSortDate;
[self.tableView reloadData];
}
}
However, its not work. How to achieve that task? (reload table with different sort order)?
Maybe this is because of
if (_frc != nil) {
return _frc;
}
you reload the table. It uses the old fetch results controller. Your self.sortOrder = tableSortServer is therefore not taken into account. I think you need set new sort descriptor (and actually perform fetch) every time before reloading your table view

Attempting to reordering UITableView using an NSFetchedResultsController

I am attempting to edit the order of my UITableView while using Core Data and an NSFetchedResultsController. As I understand, Core Data does not have a built in method for rearranging objects in a Core Data model.
The idea was to create an array, reorder my objects there, and then write that data back to my model.
NSFetchedResultsController
-(NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"List" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"listName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
moveRowAtIndexPath
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; {
NSMutableArray *toDoItems = [[self.fetchedResultsController fetchedObjects] mutableCopy];
NSManagedObject *managedObject = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];
[toDoItems removeObject:managedObject];
[toDoItems insertObject:managedObject atIndex:[destinationIndexPath row]];
int i = 0;
for (NSManagedObject *moc in toDoItems) {
[moc setValue:[NSNumber numberWithInt:i++] forKey:#"listName"];
}
[self.managedObjectContext save:nil];
}
When I hit my Edit button, I can rearrange the rows but as soon as I let the row go, my app crashes. Im not getting any kind of stack trace in the console when it crashes.
I set a breakpoint on exception and it seems to be crashing on this line
[moc setValue:[NSNumber numberWithInt:i++] forKey:#"listName"];
My key name is correct. But I now realize this is completely wrong in that I am trying to set this as a number and that shouldnt be the case.
Any suggestion or push in the right direction would be appreciated.
Either amend your code to set a string value for the listName, something like this:
[moc setValue:[NSString stringWithFormat:"%d",i++] forKey:#"listName"];
(but beware because by default this will sort as a string, so 11 comes before 2, etc).
So, better, add an integer attribute to your model, and use that to sort the fetched results controller.
Don't copy NSManagedObject. It's a managed object by core data. If you want a new one ask the context.

Problems with NSFetchedResultsController in iOS7?

I am using NSFetchedResultsController to load some data into UITableView. No matter how many objects I save, it only loads the first one. If I delete that one object from the UITableView and reload it, sometimes it will show one or more of the other objects I've saved. It's very unpredictable.
The strange thing is the code I was using worked fine on the iOS6 SDK. I know that the issue is with the NSFetchedResultsController because when I make a fetch request with NSFetchRequest, the data that is returned is correct. Here is the code;
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
{
exit(-1);
}
}
- (void)viewDidUnload
{
self.fetchedResultsController = nil;
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"HatInfo" inManagedObjectContext:[self.appDelegate managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"lastSaved" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:9];
NSArray *fetchedObjects = [[self.appDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
//returns the correct amount of objects
NSLog(#"FetchedObjects: %lu", (unsigned long)[fetchedObjects count]);
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[self.appDelegate managedObjectContext] sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
//returns the incorrect amount
return [sectionInfo numberOfObjects];
}
This kind of unexpected behavior can happen if you used the same cacheName in other part of core data code (for example: another fetchedResultsController in your app.
You can try to change the cache name to something different or nil and see what happens.
Add this line before you start fetch :
[NSFetchedResultsController deleteCacheWithName:<< catch name>>];

Custom Core Data SectionNameKeyPath

I am new at core data and am trying to figure out how to create a custom sectionNameKeyPath in my NSFetchedResultsController. I have a managed object with an attribute called acctPeriod. It is a NSString. I want to create sections based on the first 4 characters of this field. The first 4 characters represent the year of the accounting period and doesn't need to be saved.
I have gone through this site and have seen posts about transient attributes but I can't seem to get them to work. Basically I want this and then assign periodYear for my sectionNameKeyPath.
#dynamic periodYear;
-(NSString *)periodYear
{
return [self.acctPeriod substringToIndex:4];
}
Any help would be appreciated.
**UPDATE:
Using Martin R. answer, I was able to get it to work as expected.
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Billing" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"acctPeriod" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
//Predicate
NSPredicate *pred = [NSPredicate predicateWithFormat:#"clients = %#", self.client];
NSLog(#"%#",pred);
//[fetchRequest setResultType:NSDictionaryResultType];
//[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPredicate:pred];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"periodYear" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
The following should work: Implement the periodYear method (which will be used
as "section name key path") in a class extension of your managed object subclass:
#interface Event (AdditionalMethods)
- (NSString *)periodYear;
#end
#implementation Event (AdditionalMethods)
- (NSString *)periodYear {
return [self.acctPeriod substringToIndex:4];
}
#end
Make sure that acctPeriod is used as the first (or only) sort descriptor for the fetch request:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"acctPeriod" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
Use periodYear as sectionNameKeyPath for the fetched results controller:
NSFetchedResultsController *_fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"periodYear"
cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
And finally add the default titleForHeaderInSection method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}
Alternatively, you can define periodYear as transient attribute of the managed object.
It will also not be stored in the database in that case, but can be implemented in a way that the value is calculated on demand and cached.
The DateSectionTitles sample project from the Apple Developer Library demonstrates how this works.
I recommend against using a transient property as the sectionNameKeyPath as it will result in faulting all objects obtained by the fetch request just so the property could be used as the section path.
You better define a persistent property of year and use it as your sectionNameKeyPath.
set you FRC sectionNameKeyPath to year like so:
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"year"
cacheName:nil/*your chioce*/];
to display the section name as a title in the table, implement:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
id<NSFetchedResultsSectionInfo> sec = [self.fetchedResultsController sections][section];
return [sec name];
}

NSFetchedResultsController with NSPredicate in Two Different UIViewControllers - NSRangeException

I have two different views, controlled by two different view controllers. Each view has a UITableView inside it. Each UITableView is hooked to a NSFetchedResultsController. In View 1, I do not want to narrow my FRC results, so I do not use a predicate. In View 2, I use a predicate to get certain objects based on a BOOL (aka NSNumber!) value. But when I try to use this predicate, I get an NSRangeException (index 1 beyond bounds) when I segue to View 2. It also throws that exception when I switch the predicates (predicate on View 1, no predicate on View 2). And again the exception comes when I segue to View 2. There are cases where it is successful (neither of which is what I want):
1) I do not use any predicate for either fetch request.
2) I use the same predicate for the fetch request for both View 1 and View 2.
Some code for your enjoyment:
In View1.m:
-(NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSManagedObjectContext *context = [CoreDataHelper getContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"EditableSong" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"dateModified" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:5];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
In View2.m
-(NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSManagedObjectContext *context = [CoreDataHelper getContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"EditableSong" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"dateModified" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"collaborative == %#", [NSNumber numberWithInt:0]];
[fetchRequest setPredicate:myPredicate];
[fetchRequest setFetchBatchSize:5];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context sectionNameKeyPath:nil
cacheName:#"Root"];
NSLog(#"here");
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
And this is where the green error arrow (SIGABRT) points to (this code is the same in View1 and View2 by the way):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *MyIdentifier = [NSString stringWithFormat:#"MyIdentifier %i", indexPath.row];
SongTableViewCell *cell = (SongTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
EditableSong *song = [_fetchedResultsController objectAtIndexPath:indexPath];//<<<------HERE!!! SIGABRT
song.audioPlayer = [[AudioPlayer alloc] initWithSong:song];
cell = [[SongTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier editableSong:song andVC:self];
UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[playButton setBackgroundColor:[UIColor clearColor]];
[playButton setImage:[UIImage imageNamed:#"mainmenuplay.png"] forState:UIControlStateNormal];
playButton.frame = CGRectMake(300.0f, 15.0f, 65.0f, 65.0f);
[playButton addTarget:self action:#selector(playSong:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:playButton];
}
return cell;
}
Thanks!
Update 1
Here is my numberOfRowsInSection method (again, same for both classes):
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo =
[[_fetchedResultsController sections] objectAtIndex:section];
NSLog(#"%i", [sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
For reference, that log is supposed to print 3 in View1 and 1 in View2, but it prints 3 in both.
It seems you are returning the wrong number of cells. cellForRowAtIndexPath: wants to create a cell, but there is no objectAtIndexPath.
So you will need to check your numberOfRowsInSection method.
Problem solved:
Had to change the cache to nil.
See this post:
http://iphoneincubator.com/blog/data-management/delete-the-nsfetchedresultscontroller-cache-before-changing-the-nspredicate

Resources