I'm writing a small iPhone app for my company that shows bookings for each employee one week at a time. I'm using core data to get a list of 'Bookings' for a given week and want to display them in a UITableView broken down in to one section per day of the week.
The problem comes in that I need to show 7 sections for each day of the week (showing a 'No Bookings' cell where a section/date has no bookings).
I've got a screenshot of the app as it stands here (sorry can't post images yet as I'm new to StackOverlow)
At the moment I'm achieving this by using a 'fetchResults' method which gets the bookings and organises them in to an array of possible dates:
- (void)refetchResults {
// Drop bookings Array, replacing with new empty one
// 7 slots for 7 days each holding mutable array to recieve bookings where appropraite
self.bookings = [NSArray arrayWithObjects:[NSMutableArray array],
[NSMutableArray array], [NSMutableArray array],
[NSMutableArray array], [NSMutableArray array],
[NSMutableArray array], [NSMutableArray array], nil];
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Booking" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Limit to this weeks data
[fetchRequest setPredicate:
[NSPredicate predicateWithFormat:#"(date >= %#) && (date <= %#) && (resource == %#)",
firstDate,lastDate,resourceId]];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"recId" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Fetch records in to array
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (results == nil) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[fetchRequest release];
[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];
// Walk through records and place in bookings Array as required
for (Booking *item in results) {
// Decide on array index by difference in firstDate and booking date
int idx = (int)[[item date] timeIntervalSinceDate:firstDate]/86400;
// Add the item to the approp MutArray
[(NSMutableArray *)[bookings objectAtIndex:idx] addObject:item];
}
// Reload table
[tableView reloadData];
}
My question is: is there any way to achieve the same result using NSFetchedResultsController? Somehow I'd need to get the NSFetchedResultsController to have 7 sections, one for each day of the week, some of them possibly having no bookings.
Any help much appreciated :)
So, being as the weather isn't very nice outside I've had a go at answering my own question and implementing the 'workaround' described in my reply to westsider.
The idea is to hold a 'mapping' array (just a simple 7 slot int array) which will map the section the tableview will ask for to the underlying fetchedresultscontroller section. Each array slot will have the appropriate section index or '-1' where there are no underlying sections (and where a 'No Booking' cell should be shown instead).
So, my refetchResults method becomes:
- (void)refetchResults {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Booking" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Limit to this weeks data
[fetchRequest setPredicate:
[NSPredicate predicateWithFormat:#"(date >= %#) && (date <= %#) && (resource == %#)",
firstDate,lastDate,resourceId]];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"recId" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Set up FRC
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"date" cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];
// Run up FRC
NSError *error = nil;
if (![fetchedResultsController_ performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Update FRC map
[self updateFRCMap];
// Reload table
[tableView reloadData];
}
The mapping is set in the following method. This is called whenever the mapping needs to be refreshed - for example when I get callbacks from the fetchedresultscontroller for items that have been added/deleted/etc.
- (void)updateFRCMap {
// Set mapping table for seven days of week to appropriate section in frc
for (int idx=0;idx<7;idx++) { frcMap[idx] = -1; } // Reset mappings
// For each section
for (int sidx=0; sidx<[[self.fetchedResultsController sections] count]; sidx++)
{
// If section has items
if ([[[self.fetchedResultsController sections] objectAtIndex:sidx] numberOfObjects] > 0)
{
// Look at first booking of section to get date
NSDate *date = [(Booking *)[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:sidx]] date];
// Decide on array index by difference in firstDate and booking date
int idx = (int)[date timeIntervalSinceDate:firstDate]/86400;
// Set map
frcMap[idx] = sidx;
}
}
}
This can probably be optimised a bit but works OK for now. I suspect it might suffer GMT/BST clock change problems which will need fixing ... not that clock change problems are all that urgent, eh Apple? ;P
After that it's just a case of using the mapping array when responding to the tableview:
#pragma mark -
#pragma mark Table view data source
// Gets the booking from the fetchedResultsController using a remapped indexPath
- (Booking *)bookingForMappedIndexPath:(NSIndexPath *)indexPath {
return (Booking *)[self.fetchedResultsController objectAtIndexPath:
[NSIndexPath indexPathForRow:indexPath.row inSection:frcMap[indexPath.section]]];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 7; // 7 days viewed
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Rows in section or 1 if no section
if (frcMap[section] != -1) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:frcMap[section]];
return [sectionInfo numberOfObjects];
} else {
return 1;
}
}
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"RegularCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// If no actual bookings for section then its a blank cell
if (frcMap[indexPath.section] == -1) {
// Configure a blank cell.
cell.textLabel.text = #"No Bookings";
cell.detailTextLabel.text = #"";
cell.textLabel.font = [UIFont systemFontOfSize:16];
cell.textLabel.textColor = [UIColor lightGrayColor];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
} else {
// Regular cell
Booking *booking = [self bookingForMappedIndexPath:indexPath];
cell.textLabel.text = booking.desc;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# %#", booking.location, booking.detail];
cell.textLabel.font = [UIFont systemFontOfSize:14];
cell.textLabel.textColor = [UIColor darkTextColor];
cell.detailTextLabel.font = [UIFont systemFontOfSize:12];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
}
Any comments or better ways of writing this are very much welcome :)
I haven't used this much, but you might check out NSFetchedResultsSectionInfo protocol. It can be used like this, apparently:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numberOfRows = 0;
if ([[fetchedResultsController sections] count] > 0)
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}
return numberOfRows;
}
Good luck.
I had this problem too. I've written a subclass of NSFetchedResultsController to solve the issue:
https://github.com/timothyarmes/TAFetchedResultsController
Tim
Related
My table is scrolling incredibly slow, and I think it's caused by the Core Data methods within my cellForRowAtIndexPath. Below is my cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSManagedObject *info = [buildingArray objectAtIndex: [indexPath row]];
// all rooms have been scanned for building
if([self allRoomsScanned: [[info valueForKey:#"buildingid"] intValue]]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[cell.textLabel setTextColor: [UIColor lightGrayColor]];
}
else {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[cell.textLabel setTextColor: [UIColor blackColor]];
}
[cell.textLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:16.0]];
[cell.textLabel setText:[info valueForKey:#"buildingname"]];
return cell;
}
And here is the allRoomsScanned method and allDevicesScanned method:
- (BOOL) allRoomsScanned: (int) buildingID {
NSMutableArray *scannedRoomArray = [[NSMutableArray alloc] init];
// Get all user_device
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"user_device" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSNumber *deviceid = [NSNumber numberWithInt: 0];
NSNumber *roomid = [NSNumber numberWithInt: 0];
//int lastRoomID = 0;
for (NSManagedObject *info in fetchedObjects) {
// Get all device
deviceid = [info valueForKey:#"deviceid"];
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription
entityForName:#"device" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(deviceid = %d)", [deviceid intValue]];
[fetchRequest setPredicate:predicate];
NSArray *fetchedDevices = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *infod in fetchedDevices) {
// Get all room
roomid = [infod valueForKey:#"roomid"];
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription
entityForName:#"room" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(roomid = %d) AND (buildingid = %d)", [roomid intValue], buildingID];
[fetchRequest setPredicate:predicate];
NSMutableArray *fetchedRoom = [[context executeFetchRequest:fetchRequest error:&error] mutableCopy];
// add room to array if room belongs to selected building and room not already added
if([fetchedRoom count] > 0) { //&& lastRoomID != [roomid intValue]) {
for (NSManagedObject *info in fetchedRoom) {
NSLog(#"room id: %#", [info valueForKey:#"roomid"]);
// add room ids to array if not already there
if (![scannedRoomArray containsObject:[info valueForKey:#"roomid"]] && [self allDevicesScanned:[[info valueForKey:#"roomid"] intValue]])
[scannedRoomArray addObject: [info valueForKey:#"roomid"]];
}
//lastRoomID = [roomid intValue];
}
}
}
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription
entityForName:#"room" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(buildingid = %d)", buildingID];
[fetchRequest setPredicate:predicate];
NSArray *fetchedRoomTotal = [context executeFetchRequest:fetchRequest error:&error];
//NSLog(#"Total Rooms for Building: %d", [fetchedRoomTotal count]);
//NSLog(#"Scanned Rooms for Building: %d", [scannedRoomArray count]);
//NSLog(#"Scanned rooms: %#", scannedRoomArray);
if([fetchedRoomTotal count] == [scannedRoomArray count] && [fetchedRoomTotal count] > 0) {
return YES;
}
else {
return NO;
}
}
- (BOOL) allDevicesScanned: (int) roomID {
NSMutableArray *scannedDeviceArray = [[NSMutableArray alloc] init];
// Get all user_device
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"user_device" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSNumber *deviceid = [NSNumber numberWithInt: 0];
//NSNumber *roomid = [NSNumber numberWithInt: 0];
for (NSManagedObject *info in fetchedObjects) {
// Get all device
deviceid = [info valueForKey:#"deviceid"];
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription
entityForName:#"device" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(deviceid = %d) AND (roomid = %d)", [deviceid intValue], roomID];
[fetchRequest setPredicate:predicate];
NSArray *fetchedDevices = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *infod in fetchedDevices) {
// add device to array
if([fetchedDevices count] > 0) {
NSLog(#"room id: %d", roomID);
// add device ids to array if not already there
if (![scannedDeviceArray containsObject:deviceid])
[scannedDeviceArray addObject: deviceid];
}
}
}
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription
entityForName:#"device" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(roomid = %d)", roomID];
[fetchRequest setPredicate:predicate];
NSArray *fetchedDeviceTotal = [context executeFetchRequest:fetchRequest error:&error];
//NSLog(#"Total Devices for Room: %d", [fetchedDeviceTotal count]);
//NSLog(#"Scanned Devices for Room: %d", [scannedDeviceArray count]);
//NSLog(#"Scanned Devices: %#", scannedDeviceArray);
if([fetchedDeviceTotal count] == [scannedDeviceArray count] && [fetchedDeviceTotal count] > 0) {
return YES;
}
else {
return NO;
}
}
Any idea on how to get rid of the latency when scrolling? I'm assuming I may be doing something inefficiently with either my core data calls or the way I'm calling the method in cellForRowAtIndexPath.
Thank for any help. It is greatly appreciated.
You really should not make Fetch Requests and processing like that on the main thread while scrolling a table view.
As rokjarc said, you should definitely save the result of your (quite heavy) allRoomsScanned method. I'd suggest adding a new style, i.e. with an activity indicator, that the cell gets when you don't have a result for that input yet. As soon as the load is complete you refresh the table view cell.
Attention: You can't use your default NSManagedObjectContext in allRoomsScanned and allDevicesScanned.
You need to initialize a new context in the background thread. Either initialize a new context at the beginning of the block and pass it as a method parameter or create a new one right in the methods.
NSManagedObject *info = [buildingArray objectAtIndex: [indexPath row]];
NSNumber *cachedResult = [self.scanResults objectForKey:info.objectID];
if (cachedResult == nil) {
// style loading state
int scanInfo = [[info valueForKey:#"buildingid"] intValue];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL result = [self allRoomsScanned: scanInfo];
[self.scanResults setObject:[NSNumber numberWithBool:result] forKey:info.objectID];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]
});
} else if (cachedResult.boolValue == YES) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[cell.textLabel setTextColor: [UIColor lightGrayColor]];
} else if (cachedResult.boolValue == NO) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[cell.textLabel setTextColor: [UIColor blackColor]];
}
[cell.textLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:16.0]];
[cell.textLabel setText:[info valueForKey:#"buildingname"]];
return cell;
I have a UITableView populated (previously saved via a button in navbar), with transactions. Each row has five UILabel: date, description, person, value (deposits and withdraws) balance. The table is sort by date. How can I obtain the daily balance? The balance is the sum (+ and -) from the first input up to the current row.
I have the following code (JournalDetail.m), but I only get the same amount as value, instead of a cumulative amount (balance)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
GxPolizasL *cell = (GxPolizasL *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"GxPolizasL" owner:self options:nil];
cell = gxPolizasL;
self.gxPolizasL = nil;
GMDiario *gmDiarioy = (GMDiario *)[self.fetchedResultsController objectAtIndexPath:indexPath];
//Date value
NSDateFormatter* df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"dd/MM/yy"];
cell.fechao.text = [df stringFromDate:gmDiarioy.pzFecha];
[df release];
//Description value
cell.referencia.text=gmDiarioy.pzAlias;
//Person value
cell.item.text = gmDiarioy.persona.prAlias;
//Transaction value
NSNumberFormatter* vf = [[NSNumberFormatter alloc] init];
[vf setNumberStyle:NSNumberFormatterCurrencyStyle];
cell.valuem.text= [vf stringFromNumber: gmDiarioy.pzMont01];
[vf release];
//This is where the balance value is intended to be created
NSDecimalNumber *saldoAc = [NSDecimalNumber decimalNumberWithString:#"0.0"];
NSDecimalNumber *objectExpenseNumber = [gmDiarioy valueForKeyPath:#"pzMont01"];
saldoAc = [saldoAc decimalNumberByAdding:objectExpenseNumber];
NSLog(#"saldoAc: %#",saldoAc);
NSNumberFormatter* af = [[NSNumberFormatter alloc] init];
[af setNumberStyle:NSNumberFormatterCurrencyStyle];
cell.valuea.text= [af stringFromNumber: saldoAc];
[af release];
}
return cell;}
Could you help me to figure out which is the correct way to implement balance value? Thanks in advance. Your help is greatly appreciated.
This is the code for self.fetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
//Diario is an entity that keeps all transactions
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Diario" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
//This is to select only the journals for the current account (value passed at didSelectRowAtIndexPath from the previous UItableView
NSString *filtro=gmCuenta.cnAlias;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"cuenta.cnAlias like %#", filtro];
NSLog(#"NSPredicate1 %#", gmDiario.cuenta);
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"pzFecha" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil] autorelease];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();}
return __fetchedResultsController;
}
Basically what you want to do is this: Each time cellForRowAtIndexPath: gets called you want to loop through your data source array and sum all the values starting at the beginning of the array and ending with the array item equal to the row # (i.e. indexPath.row) of the cell in question. That will give you a running total (balance).
NSDecimalNumber *saldoAc = [NSDecimalNumber decimalNumberWithString:#"0.0"];
for (int i=0; i <= indexPath.row; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:i];
GMDiario *tempObj = (GMDiario *)[self.fetchedResultsController objectAtIndexPath:indexPath];
NSDecimalNumber *objectExpenseNumber = [tempObj valueForKeyPath:#"pzMont01"];
saldoAc = [saldoAc decimalNumberByAdding:objectExpenseNumber];
}
NSNumberFormatter* af = [[NSNumberFormatter alloc] init];
[af setNumberStyle:NSNumberFormatterCurrencyStyle];
cell.valuea.text= [af stringFromNumber: saldoAc];
[af release];
One other comment - you should be setting the values of your cell's subviews after you exit the if (cell == nil) block. Otherwise you will run into trouble when your tableView starts scrolling.
I have implemented a Section Index for my tableview. It returns the correct letters for what is stored in coredata (A B C D F G H I K M N O P Q R S T U V W). However the indexes are off.
If I click on the letter M it takes me to letter I, etc.. The name of the index I am attempting to sort on is name
What is there for me to do to fix the index?
I am also implementing numberOfSectionsInTableView,numberOfRowsInSection, and titeForHeaderInSection. The section headers are displayed correctly.
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
NSArray * sectionTitlesArray = [fetchedResultsController sectionIndexTitles];
NSMutableArray *newTitles = [[NSMutableArray alloc] init];
for (NSString *state in sectionTitlesArray) {
[newTitles addObject:[NSString stringWithFormat:#"%#", state]];
}
return [newTitles autorelease];
}
Including the FRC just in case it is relevant:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"WidgetCoItems" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1,sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
}
return fetchedResultsController;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
NSMutableArray *indices = [NSMutableArray array];
id <NSFetchedResultsSectionInfo> sectionInfo;
for( sectionInfo in [fetchedResultsController sections] )
{
[indices addObject:[sectionInfo name]];
}
return indices;
}
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
{
return [[[fetchedResultsController sections] objectAtIndex:section] name];
}
Try to implement in this way.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if ([[<#Fetched results controller#> sections] count] > 0) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[<#Fetched results controller#> sections] objectAtIndex:section];
return [sectionInfo name];
} else
return nil;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [<#Fetched results controller#> sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [<#Fetched results controller#> sectionForSectionIndexTitle:title atIndex:index];
}
i generate a tableview using core-data and a NSFetchedResultsController with sectionNameKeyPath. My Core-Data entities look fine, also in the SQL-Database the Data looks good.
The Entity is called "Cast" and looks like this:
Cast
-> job
-> department // the attribute i want the sections from
i generate my NSFetchedResultsController like this
// fetch controller
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Cast" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort1 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sort2 = [[NSSortDescriptor alloc] initWithKey:#"job" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, sort2, nil]];
[sort1 release];
[sort2 release];
// Predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"movie == %#", self.movie];
[fetchRequest setPredicate:predicate];
// Generate it
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"department"
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
[fetchRequest release];
[theFetchedResultsController release];
// Fetch Casts
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
XLog("Unresolved error %#, %#", error, [error userInfo]);
}
But the result is the following (i added the "department" attribute into the detail attribute to show the problem)
as you can see. the sections are generated properly, but then the single entities are completely random inserted into the sections.
can anybody see a bug in my code?
here is the rest of the code that is related to the cell/section stuff
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = nil;
sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
Cast *currentCast = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = currentCast.name;
//cell.detailTextLabel.text = currentCast.job;
// just temporary
cell.detailTextLabel.text = currentCast.department;
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *jobTitle = [[[fetchedResultsController sections] objectAtIndex:section] name];
return jobTitle;
}
thanks for all hints.
please leave a comment if something is unclear.
You should sort by department first.
NSSortDescriptor *sort1 = [[NSSortDescriptor alloc] initWithKey:#"department" ascending:YES];
NSSortDescriptor *sort2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sort2 = [[NSSortDescriptor alloc] initWithKey:#"job" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, sort2, sort3, nil]];
[sort1 release];
[sort2 release];
[sort3 release];
So i'm building a small application, it uses core data database of ~25mb size with 4 entities. It's for bus timetables.
In one table named "Stop" there are ~1300 entries of bus stops with atributes "name", "id", "longitude", "latitude" and couple relationships. Now there are many stops with identical name attribute but different coordinates and id. So I want to show all distinct stop names in table view, i'm using setReturnsDistinctResults with NSDictionaryResultType and setPropertiesToFetch. But setReturnsDistinctResult is not working and I'm still getting all entries.
Heres code:
- (NSFetchRequest *)fetchRequest {
if (fetchRequest == nil) {
fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Stop" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES] autorelease]];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:[[entity propertiesByName] objectForKey:#"name"]]];
[fetchRequest setReturnsDistinctResults:YES];
DebugLog(#"fetchRequest initialized");
}
return fetchRequest;
}
/////////////////////
- (NSFetchedResultsController *)fetchedResultsController {
if (self.predicateString != nil) {
self.predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %#", self.predicateString];
[self.fetchRequest setPredicate:predicate];
} else {
self.predicate = nil;
[self.fetchRequest setPredicate:predicate];
}
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:sectionNameKeyPath cacheName:nil];
return fetchedResultsController;
}
//////////////
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [[fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"name"];
return cell;
}
This may be late for you, but may help others.
I had the same problem. What I found is that, if persistant store type is NSSQLiteStoreType the returnDistinctResults works. But for NSXMLStoreType distinct values are not working.
I haven't tested results for NSBinaryStoreType and NSInMemoryStoreType.