user2512523
user2512523

Reputation: 1359

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

Upvotes: 0

Views: 382

Answers (1)

Martin R
Martin R

Reputation: 539735

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

Upvotes: 0

Related Questions