Core-Data NSFetchedResultsController to-many relationship - ios

Here is my Datamodel:
I filled the Database with Data.
Now I want to display this in a tableview.
First I want to select a day with the 'days' attribute, then get its lessons.
I want to display the attributes 'end' and 'start' in the header (-(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section)
Then I want to display the lesson data in the right section. How can I do that with NSFetchedResultsController?
I've set up a basic code:
NSFetchRequest* fetch = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([Lessons class])];
NSSortDescriptor* sortGroup = [NSSortDescriptor sortDescriptorWithKey:#"lesson" ascending:YES];
fetch.sortDescriptors = #[sortGroup];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
NSError* error;
[controller performFetch:&error];
if (error) {
NSLog(#"Error: %#", error);
abort();
}
EDIT:
Here is the Storyboard:
UPDATE
Here's my Code:
TableView.m (important Methods)
-(id)init
{
self = [super init];
if (self) {
NSLog(#"%s",__PRETTY_FUNCTION__);
self.del = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = self.del.managedObjectContext;
NSFetchRequest* fetch = [NSFetchRequest fetchRequestWithEntityName:#"Lessons"];
NSString *theSelectedDay = #"Mi";
NSPredicate *pred = [NSPredicate predicateWithFormat:#"lessonToDay.day == %#", theSelectedDay];
fetch.predicate = pred;
NSSortDescriptor *sortTime = [NSSortDescriptor sortDescriptorWithKey:#"lessonToTime.start" ascending:YES];
//NSSortDescriptor *sortGroup = [NSSortDescriptor sortDescriptorWithKey:#"lesson" ascending:YES];
fetch.sortDescriptors = #[sortTime];
self.controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"lessonToTime.start"
cacheName:nil];
}
return self;
}
#pragma mark - Table view data source
- (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 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
// Configure the cell...
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Lessons *lesson = [self.controller objectAtIndexPath:indexPath];
cell.textLabel.text = lesson.lesson;
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
// This is the first lesson in this section:
Lessons *lesson = [sectionInfo objects][0];
//For testing I changed the type of 'start' and 'end' to string
NSLog(#"start: %#",lesson.lessonToTime.start);
NSLog(#"end: %#", lesson.lessonToTime.end );
NSString *title = [NSString stringWithFormat:#"%#-%#",
lesson.lessonToTime.start,
lesson.lessonToTime.end];
return title;
}
RESULT:

First of all you have to add a predicate that only the lessons for the selected day are displayed:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"lessonToDay.day == %#", theSelectedDay];
fetch.predicate = pred;
To group the table view into sections, you have to set the sectionNameKeyPath: parameter
of the fetched results controller, for example to #"lessonToTime.start". This would group
all lessons with the same start time into one section.
(As I understand your comment, lessons with the same start time have also the same end time,
so this should be sufficient.)
The same key path must be used as first sort descriptor:
NSSortDescriptor *sortTime = [NSSortDescriptor sortDescriptorWithKey:#"lessonToTime.start" ascending:YES];
NSSortDescriptor *sortGroup = [NSSortDescriptor sortDescriptorWithKey:#"lesson" ascending:YES];
fetch.sortDescriptors = #[sortTime, sortGroup];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"lessonToTime.start"
cacheName:nil];
To display the section header according to your needs, you have to override titleForHeaderInSection::
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
// This is the first lesson in this section:
Lesson *lesson = [sectionInfo objects][0];
// Now build some title from lesson.lessonToTime.start and lesson.lessonToTime.end:
NSString *title = ...;
return title;
}

You can do this with two View Controllers:
DaysViewController - here you show all days. When user selects the cell you get the day associated with the cell and pass it to the second View Controller,
LessonsViewController - here you use the passed object when building your NSFetchRequest's predicate
UPDATE: my answer isn't correct anymore, because the problem's description have changed.

Related

TableView Sections sort by expires next

so I'm working on a litte ToDo app.
The TableView shows a CoreData Entity with the attributes of the name(string) and the Date(Date).
Currently the NSfetchedResultsController sort the tableView by the Date of the ToDo, but i also want some sections for example "expired ToDo's - which have a date in the past" or "ToDo's for the next week"
how can I achieve this?
Code NSFetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
// Create and configure a fetch request with the Book entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Inventory" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *productDescriptor = [[NSSortDescriptor alloc] initWithKey:#"inventoryProductName" ascending:YES];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"expireDate" ascending:YES];
NSArray *sortDescriptors = #[dateDescriptor,productDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"inventoryProductName" cacheName:#"Root"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Code TableView:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Inventory *inventory = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = inventory.inventoryProductName;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterShortStyle];
NSDate *dt = inventory.expireDate;
NSString *dateAsString = [formatter stringFromDate:dt];
//[formatter release];
cell.detailTextLabel.text = [NSString stringWithFormat:#"Expires at: %#", dateAsString];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
You need to give your todo items another property that reflects what you want. As for "past", "next week" you could use a transient property that is calculated on the fly via a custom getter. In some cases, you will have to actually persist this as a (non-transient) attribute for your fetched results controller to work.
For predictable sorting, the actual attribute value could just be a number - this would allow you to also include date-independent categories, such as "canceled", "invalid", etc.

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

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.

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];

Show sections with core data

I want to code a todo app and display 2 sections. 1 for entrys, that have to be done and 1 for already done things.
I want something like that:
Things not done yet
Todo 1
Todo 2
Things already done
Todo 3
Todo 4
At the moment I have 1 section for the undone todos.
My code for the table view at the moment looks like this
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSArray *array = [[NSArray alloc] initWithObjects:#"To do",#"done",nil];
return [array objectAtIndex:section];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
And my code for the fetchedresultscontroller looks like this:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSManagedObjectContext* context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Todo" inManagedObjectContext:context];
[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:#"timestamp" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"done == %#", #(NO)];
[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:self.managedObjectContext sectionNameKeyPath:#"done" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
Objects with the key "NO" are not done and with the key "YES" are done. But I don't know how to display both values in different sections.
Edit
I found the solution to my problem…
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"done" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
That solved this problem. But now sometimes an undone item shows up at done.
Or if there are two done items they appear under "Todo"
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSArray *array = [[NSArray alloc] initWithObjects:#"Todo",#"Done",nil];
return [array objectAtIndex:section];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Thats my actual code for that…
Now it looks like that. Undone todo unchecked, done todo checked. http://img.xnmn.de/i/2353ad.png
But if I check undone todo as done, they both appear at "todo" http://img.xnmn.de/i/5edff4.png
You're code looks almost right. Since you set the sectionKeyPath to "done", the fetched results controller will automatically create the sections for you. You could add another sort descriptor for the "done" attribute to sort the entries according to their section.
What you have to do is to remove the NSPredicate. In you current code, you're filtering out all entries with "done == YES", but since you have your two sections, you want the results controller to return all entries.
So basically you just need to remove the
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"done == %#", #(NO)];
[fetchRequest setPredicate:predicate];
Of course you'd also have to implement the – tableView:cellForRowAtIndexPath: method
If i get your Code right you already created the section which is fine.
- (UITableViewCell *)tableView:(UITableView *)inTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Entry";
UITableViewCell *cell = [inTableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
YourObject *cellEntry = [_fetchedResultsController objectAtIndexPath:indexPath];
//init cell
}
This should choose the right entry for the right section otherwise the line where you set the section is wrong.
The index path chooses the right section.

Resources