fetchedResultsController.fetchedObjects.count = 0 but it is full of objects - ios

I am using pretty standard implementation of fetchedResultsController for output in tableView. In the end of -viewDidLoad I am making first call:
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error])
{
NSLog(#"Error! %#",error);
abort();
}
this is my fetchedResultsController:
- (NSFetchedResultsController *) fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Preparation"
inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
int i = 1;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ismer == %d", i];
fetchRequest.predicate = predicate;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" 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;
NSLog(#"_fetchedResultsController.fetchedObjects.count - %d", _fetchedResultsController.fetchedObjects.count);
return _fetchedResultsController;
}
my tableView methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections]count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [secInfo numberOfObjects];
}
- (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];
}
CDPreparation *drug = (CDPreparation *)[self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"%#", drug.name];
return cell;
}
So, question is:
in log of _fetchedResultsController.fetchedObjects.count is equal to 0, but visually tableView is filled with objects. Why I have two different results for count?

An NSFetchedResultsController doesn't actually perform the fetch request until you call performFetch:, so the result count is 0.
If you log fetchedObjects.count after calling performFetch:, you'll see a number which matches the tableView row count.

Related

IOS XMPP how to go next controller with particular username and chat

I am working on XMPP project. i have successfully completed login process and online ofline rosters. but now i dont know how to go to next view controller with particular user's field and chat with him. here is my try.
Now what i have to write in UITableview's Delegate Method
friendsviewcontroller.m file // Fetch online and offline rosterlist
#pragma mark -Tableview datasource method
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[[self fetchedResultsController] sections] count];
}
- (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row){
case 0:
if(indexPath.section==0)
return 60.0; // first row is 123pt high
default:
return 60.0; // all other rows are 40pt high
}
}
- (NSString *)tableView:(UITableView *)sender titleForHeaderInSection:(NSInteger)sectionIndex
{
NSArray *sections = [[self fetchedResultsController] sections];
if (sectionIndex < [sections count])
{
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:sectionIndex];
int section = [sectionInfo.name intValue];
switch (section)
{
case 0 : return #"Available";
case 1 : return #"Away";
default : return #"Offline";
}
}
return #"";}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex{
NSArray *sections = [[self fetchedResultsController] sections];
if (sectionIndex < [sections count])
{
id <NSFetchedResultsSectionInfo> sectionInfo = sections[sectionIndex];
return sectionInfo.numberOfObjects;
}
return 0;}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if( cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier];
}
XMPPUserCoreDataStorageObject *user = [[self fetchedResultsController] objectAtIndexPath:indexPath];
cell.textLabel.text = user.displayName;
[self configurePhotoForCell:cell user:user];
// cell.detailTextLabel.text= [self.tblchathistory objectAtIndex:indexPath.row];
cell.textLabel.textColor=[UIColor whiteColor];
return cell;
}
#pragma Mark - segue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"chathistory"])
{
CreateGroupViewController *vc = [segue destinationViewController];
}
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
NSXMLElement *queryElement = [iq elementForName: #"query" xmlns: #"jabber:iq:roster"];
if (queryElement)
{
NSArray *itemElements = [queryElement elementsForName: #"item"];
for (int i=0; i<[itemElements count]; i++)
{
NSLog(#"Friend: %#",[[itemElements[i] attributeForName:#"jid"]stringValue]);
}
}
return NO;
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController == nil)
{
NSManagedObjectContext *moc = [[self appDelegate] managedObjectContext_roster];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"XMPPUserCoreDataStorageObject"
inManagedObjectContext:moc];
NSSortDescriptor *sd1 = [[NSSortDescriptor alloc] initWithKey:#"sectionNum" ascending:YES];
NSSortDescriptor *sd2 = [[NSSortDescriptor alloc] initWithKey:#"displayName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sd1, sd2, nil];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:10];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"sectionNum"
cacheName:nil];
[fetchedResultsController setDelegate:self];
NSError *error = nil;
if (![fetchedResultsController performFetch:&error])
{
DDLogError(#"Error performing fetch:= %#", error);
//NSLog(#"error = %#", error);
}
}
return fetchedResultsController;
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[[self tblvwbuddy] reloadData];
}
You can fetch the user by following way
While you are showing in one screen that means you have the jid of the user, so take that user's jid and in next controller you can filter the user.
Here is my code to filter the porticular user from XMPPUserCoreDataStorageObject by means of jid. In my case friendJid is the jid to be filtered
- (XMPPUserCoreDataStorageObject *)fetchTheUser
{
NSManagedObjectContext *moc = [APP_DELEGATE managedObjectContext_roster];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"XMPPUserCoreDataStorageObject"
inManagedObjectContext:moc];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"jidStr=%#",[self.friendJid lowercaseString]];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:pred];
NSError *error = nil;
[fetchRequest setPredicate:pred];
NSArray *fetchedObjects = [[APP_DELEGATE managedObjectContext_roster] executeFetchRequest:fetchRequest error:&error];
XMPPUserCoreDataStorageObject *objTemp = fetchedObjects.count?[fetchedObjects objectAtIndex:0]:nil;
return objTemp;
}
Hope it will help you.
accept the answer if you find this useful

iOS, group table view, grouped by one core data entity

I have (order) table View controller, sorted by "code" entity
What I want to do is a grouped table view, grouped by
order.customer.name to be like this
[customer one]
*order.code1 - product1
*order.code2 - product1
[customer two]
...
What I have right know in viewWillAppear is:
- (void)viewWillAppear:(BOOL)animated{
NSEntityDescription *orderDescription = [NSEntityDescription entityForName: #"Order"
inManagedObjectContext:self.context];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:orderDescription];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"code" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
self.controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.context
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
[self.controller performFetch:&error];
}
this might be usefull as well
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
NSManagedObject *managedObject = [self.controller objectAtIndexPath:indexPath];
cell.textLabel.text = ((Order *) managedObject).code;
cell.detailTextLabel.text = ((Order *) managedObject).product.name;
return cell;
}
I found some solutions for group table view, but most of it isn't for core data
Thanks
Is this what you want?
- (void)setupFetch:(NSManagedObjectContext*)context {
NSEntityDescription *orderDescription = [NSEntityDescription entityForName: #"Order"
inManagedObjectContext:context];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:orderDescription];
NSSortDescriptor *sortDescriptorSection = [[NSSortDescriptor alloc] initWithKey:#"customer.name" ascending:YES];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"code" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorSection, sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
self.controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:#"customer.name"
cacheName:nil];
NSError *error;
[self.controller performFetch:&error];
[self.tableView reloadData];
}
That should create groups based on customer name but sort according to code.
Edit: To clarify. You want to use the sectionNameKeyPath to achieve what you want. Note that you need to use the sections in the NSFetchedResultsController something like so:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [self.controller.sections objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.controller.sections count];
}
I have a similar case for Rooms and Devices. My sections are Rooms and my cells are Devices, sorted first my RoomName and then by DeviceName
- (NSFetchedResultsController *)fetchedResultsController
{
if( !fetchedResultsController )
{
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Device"
inManagedObjectContext:del.managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate. This will sort the sections (setting this attribute as sectionNameKeyPath)
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"room.roomName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"deviceName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[sortDescriptor1 release];
[sortDescriptor2 release];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate* predicate = ...; // selecting devices by filter
[fetchRequest setPredicate:predicate];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"room.roomName"
cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptors release];
}
return fetchedResultsController;
}
this is used like following:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSError *error = nil;
if( ![[self fetchedResultsController] performFetch:&error] )
{
// do error handling
abort();
}
[self.tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
int nor = [[fetchedResultsController sections] count];
return nor;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
// I have a Translation class for internationalisation
return [Translation forKey:[[[fetchedResultsController sections] objectAtIndex:section] name]];
}
- (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;
}
and using in the cell like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Device *device = [fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:...];
if( !cell )
{
cell = ...
// and so on
}
return cell;
}

Performance issues using NSFetchedResultsController with sectionNameKeyPath

I have a huge amount of data inside CoreData (around 10.000 objects).
I want to show them in sections, therefore I'm using:
+ (NSFetchedResultsController*) eventsFetchResultControllerWithSearchString:(NSString*) searchString andFilter:(EventFilters) filter
{
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"ts" ascending:NO];
NSArray* sortDescriptors = #[sort];
NSPredicate* filterPredicate = [NSFetchedResultsController eventsPredicatesWithSearchString:searchString andFilter:filter];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *callEntity = [NSEntityDescription entityForName:#"EventDB" inManagedObjectContext:[[AppManager sharedAppManager] managedObjectContext]];
[fetchRequest setEntity:callEntity];
[fetchRequest setPredicate:filterPredicate];
[fetchRequest setFetchBatchSize:20];
[fetchRequest setSortDescriptors:sortDescriptors];
NSString* sectionKeyPath = #"timestamp.relativeDate";
if(searchString)
{
sectionKeyPath = nil;
}
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[[AppManager sharedAppManager] managedObjectContext]
sectionNameKeyPath:sectionKeyPath
cacheName:nil];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return aFetchedResultsController;
}
On my UITableView I have:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat emptyCellHeight = [super tableView:tableView noDataCellHieghtAtIndexPath:indexPath forCellType:kUITableViewCellTypeNoDataCell];
if(emptyCellHeight != 0.0f)
{
return emptyCellHeight;
}
if(self.measurementCell == nil)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"EventCell" owner:self options:nil];
self.measurementCell = [topLevelObjects objectAtIndex:0];
}
EventDB *event = [[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath];
return [self.measurementCell calculateHeightForEvent:event];
}
- (CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100.0f;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if(self.tableView == tableView && [[self fetchedResultsControllerForTableView:tableView].sections count] == 0)
{
return 1;
}
return [[[self fetchedResultsControllerForTableView:tableView] sections] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray* sections = [[self fetchedResultsControllerForTableView:tableView] sections];
if(section < [sections count])
{
id <NSFetchedResultsSectionInfo> theSection = [sections objectAtIndex:section];
return [theSection name];
}
return nil;
}
I found a solution to improve height calculations using estimatedHeight, but when I turned on sectionNameKeyPath it got really slow.
I suspect it is because it loads all elements to read all sections at start.
Is there any way to optimize that part ? Something like estimatedHeight but for sections ?

iOS UITableView sections with fetchedResultsController confusion

I have an entity being displayed in a table view in just one section. The entity has two attributes, workoutName and trainingLevel. Both are of string type. Training level consists of the 3 types: 1, 2, 3. (trainingLevel = (Integer 16 or String Type? Which would be ideal?) I would like to split the table into three sections, each section containing entries for the corresponding training level.
How do I do this? The code I am currently using is below:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.workoutType.workouts.count;
}
- (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];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutSet *workoutSet = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = workoutSet.workoutName;
cell.detailTextLabel.text = [NSString stringWithFormat:#"(%d)", workoutSet.days.count];
}
-(void)fetchWorkoutSets
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"WorkoutSet"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workoutType = %#", self.workoutType];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"workoutName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
[fetchRequest setPredicate:predicate];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
What I am struggling with is:
How to determine the number of rows for each section through the core data model by fetching number of entries with training level 1 or 2 or 3.
How to fill the rows of each section by fetching the correct items.
How to give a title to each section header.
Here is a good tutorial on using fetchedResultsControllers: http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller
Create some properties to hold your context and fetches:
#property (nonatomic,strong)NSManagedObjectContext* managedObjectContext;
#property (nonatomic,retain)NSFetchedResultsController *fetchedResultsController;
In your fetchedResultsController property, use sectionKeyNamePath to set up your fetched results in sections:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Workouts"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"workoutName" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"trainingLevel"
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Your initial population of your fetchedResultsController can happen in your -viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
You'd then return the number of sections and number of rows in sections like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo =
[[[self fetchedResultsController] sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
You can then get your managed object for the particular row like this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// init the cell
// and whatever other setup needed
WorkoutSet *workoutSet =
[self.fetchedResultsController objectAtIndexPath:indexPath];
// configure the cell from the managedObject properties
}

Passing one to many core data between view controllers

I am having problems passing data between view controllers.
All three view controllers are table views.
WorkoutTypeVC
WorkoutSetVC
WorkoutExerciseVC
I have three entities,
WorkoutType
workouts(->WorkoutSet) One to Many
WorkoutSet
exercises(->WorkoutExercise) One to Many
workoutType(->WorkoutType) Inverse
WorkoutExercise
workoutSet(->WorkoutSet) Inverse
I am able to switch between all three view controllers, WorkoutTypeVC loads correctly showing all entries, When selected WorkoutSetVC is loaded showing the correct entries corresponding to the selection made from WorkoutTypeVC.
But when i select an entry from WorkoutSetVC, WorkoutExerciseVC loads but is empty, Even the title of the selection doesn't load.
I have used the same code which i used when switching from WorkoutTypeVC and WorkoutSetVC.
Below is the code for switching views in WorkoutType.m file:
-(void)fetchWorkoutTypes
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutType"];
NSString *cacheName = [#"WorkoutType" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"workoutType" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (void)viewDidAppear:(BOOL)animated{
[self fetchWorkoutTypes];
[self.tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.fetchedResultsController.fetchedObjects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutType *workoutType = (WorkoutType *)[self.fetchedResultsController
objectAtIndexPath:indexPath];
cell.textLabel.text = workoutType.workoutType;
cell.detailTextLabel.text = [NSString stringWithFormat:#"(%d)", workoutType.workouts.count];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
WorkoutType *workoutType = (WorkoutType *)[self.fetchedResultsController objectAtIndexPath:indexPath];
WorkoutSetViewController *detailViewController = [[WorkoutSetViewController alloc] initWithWorkoutType:workoutType];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Below is the code for WorkoutSetVC.m
-(void)fetchWorkoutSets
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutSet"];
NSString *cacheName = [#"WorkoutSet" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"workoutName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (id)initWithWorkoutType:(WorkoutType *)workoutType
{
self = [super initWithStyle:UITableViewStylePlain];
if (self)
{
self.workoutType = workoutType;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = self.workoutType.workoutType;
[self fetchWorkoutSets];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.workoutType.workouts.count;
}
- (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];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutSet *workoutSet = [self.workoutType.workouts.allObjects objectAtIndex:indexPath.row];
cell.textLabel.text = workoutSet.workoutName;
cell.detailTextLabel.text = [NSString stringWithFormat:#"(%d)", workoutSet.exercises.count];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
WorkoutSet *workoutSet = (WorkoutSet *)[self.fetchedResultsController objectAtIndexPath:indexPath];
WorkoutExerciseTableViewController *detailViewController = [[WorkoutExerciseTableViewController alloc] initWithWorkoutSet:workoutSet];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Below is the code for WorkoutExercise.m
- (id)initWithWorkoutSet:(WorkoutSet *)workoutSet
{
self = [super initWithStyle:UITableViewStylePlain];
if (self)
{
self.workoutSet = workoutSet;
}
return self;
}
-(void)fetchWorkoutExercises
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutExercise"];
NSString *cacheName = [#"WorkoutExercise" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"exerciseName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = self.workoutSet.workoutName;
[self fetchWorkoutExercises];
}
- (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];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutExercise *exercise = [self.workoutSet.exercises.allObjects objectAtIndex:indexPath.row];
cell.textLabel.text = exercise.exerciseName;
return cell;
}
Not sure as to what i need to do for the third view controller to list all the entries, Even the title for the third view controller doesn't load which is coded in the ViewDidLoad Method.
Thank You
The problem is (I assume) the inconsistent use of data sources in the second (and third?)
view controller.
In your WorkoutSetViewController, cellForRowAtIndexPath accesses the objects directly via self.workoutType.workouts.allObjects, but didSelectRowAtIndexPath uses a fetched results controller (FRC). This does not make sense. If the table view is driven by a FRC, all data source methods must use the FRC.
Perhaps self.fetchedResultsController is nil in the second view controller?
Then the workoutSet passed to the third view controller would be nil, which would
explain that no title is set and no objects are displayed.
And generating an array from self.workoutType.workouts with allObjects is also problematic, because the order of the array elements can be random.
The second view controller should use a fetched results controller to display
all WorkoutSet objects related to the given workoutType.
And the third view controller should use a fetched results controller to display
all WorkoutExercise objects related to the given workoutSet.
UPDATE: fetchWorkoutSets in WorkoutSetVC.m should look like this:
-(void)fetchWorkoutSets
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"WorkoutSet"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workoutType = %#", self.workoutType];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"workoutName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
The predicate is important to fetch only workout sets that are related to self.workoutType.
And similarly, fetchWorkoutExercises in WorkoutExercise.m would use the predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workoutSet = %#", self.workoutSet];
[fetchRequest setPredicate:predicate];
to fetch only exercises that are related to self.workoutSet.
For the data source methods, have a look at the NSFetchedResultsController documentation, it contains sample code that you can copy
and adapt to your needs. Or you create a fresh Xcode iOS application with the "Master-Detail Application" template and select the "Core Data" checkbox. That will also give you
sample code.
For example, in cellForRowAtIndexPath in WorkoutSetVC.m you would replace
WorkoutSet *workoutSet = [self.workoutType.workouts.allObjects objectAtIndex:indexPath.row];
by
WorkoutSet *workoutSet = [self.fetchedResultsController objectAtIndexPath:indexPath];

Resources