2

I'm presenting a VC modally, then I dismiss it when a cell is selected along with call a method from the original VC.

The problem now is that nav and routineTableViewController NSLog as null. How I present the model VC:

NSString *selectedRow = [[self.exerciseArray objectAtIndex:selectedRowIndex.row]objectForKey:@"exerciseName"];
UINavigationController *nav = self.parentViewController.navigationController;
RoutineDayTableViewController *routineDayTableViewController = (RoutineDayTableViewController *) [nav topViewController];
routineDayTableViewController.muscleURL = self.muscleURL;
[routineDayTableViewController addExercise];
[self.parentViewController dismissModalViewControllerAnimated:YES];

Hierarchy of VC: RoutineDayTableViewController -> presents Modally MusclesTableViewController, then pushes to child table view, which dismisses back to RoutineDayTableViewController.

Edit:

-(void)addExercise
{       
    PFObject *exerciseInRoutine = [[PFObject alloc] initWithClassName:@"exerciseInRoutine"];
    [exerciseInRoutine setObject:self.selectedExercise forKey:@"name"]; 
    [exerciseInRoutine setObject:self.muscleName forKey:@"muscle"]; 
    [exerciseInRoutine setObject:self.muscleURL forKey:@"picture"]; 
    [exerciseInRoutine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (!error) {
            [self.tableView reloadData];
        } else {
            NSLog(@"Error: %@ %@", error, [error userInfo]);
        }
    }];   
}

1 Answers1

2

See my reply here: How to save nsdictionary of a subview to a mainview based off tableviewcell selection

The ideal solution is to create a protocol and use a delegate to pass information backwards in your view controller hierarchy. This avoids the tight coupling which is happening in your example when you link back to self.parentViewController.navigationController. What happens if the parent view controller changes in the future or you want to resuse the selection controller elsewhere in the app? The new view controller that is the parent could be an instance of a class other than RoutineDayTableViewController and may not have a exerciseURL property.

For this specific example, I would do something like the following:

Add the protocol definition inside SpecificExerciseTableViewController.h

@class SpecificExerciseTableViewController;

@protocol SpecificExerciseTableViewControllerDelegate <NSObject>
@optional
- (void)specificExerciseTableViewController:(SpecificExerciseTableViewController *)specificExerciseTableViewController didSelectSpecificExerciseWithURL:(NSURL *)exerciseURL;
@end

Add a property inside MuscleTableViewController.h

@property (nonatomic, weak) id delegate;

Add a property inside SpecificExerciseTableViewController.h

@property (nonatomic, weak) id<SpecificExerciseTableViewControllerDelegate> delegate;

Inside RoutineDayTableViewController.h

#import "SpecificExerciseTableViewController.h"

@interface RoutineDayTableViewController <SpecificExerciseTableViewControllerDelegate>

Inside RoutineDayTableViewController.m when allocating a MusclesTableViewController instance:

MuscleTableViewController *muscleTableViewController = [[MuscleTableViewController alloc] init];
muscleTableViewController.delegate = self;

Inside MuscleTableViewController.m when allocating a SpecificExerciseTableViewController instance:

SpecificExerciseTableViewController *specificExerciseTableViewController = [[SpecificExerciseTableViewController alloc] init];
specificExerciseTableViewController.delegate = self.delegate;

Inside SpecificExerciseTableViewController.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([self.delegate respondsToSelector:@selector(specificExerciseTableViewController:didSelectSpecificExerciseWithURL:)])
    {
        NSURL *selectedExerciseURL = [[self.exerciseArray objectAtIndex:selectedRowIndex.row] objectForKey:@"exerciseName"];
        [self.delegate specificExerciseTableViewController:self didSelectSpecificExerciseWithURL:selectedExerciseURL];
        [self dismissModalViewControllerAnimated:YES];
    }
}

Finally, in your RoutineDayTableViewController.m

- (void)specificExerciseTableViewController:(SpecificExerciseTableViewController *)specificExerciseTableViewController didSelectSpecificExerciseWithURL:(NSURL *)exerciseURL;
{
    [self addExercise];
}

For an example of this in practice in a video see:

00:30:00 min mark in WWDC 2011 Video - Introducing Interface Builder Storyboarding

Community
  • 1
  • 1
Andrew
  • 7,630
  • 3
  • 42
  • 51
  • I've updated my answer with an example tailored to your situation. – Andrew Nov 01 '11 at 05:39
  • I'm getting error that RoutineDayTableViewController.h cannot find the declaration protocall. I put the declaration before the interface in MuscleTableViewController right? –  Nov 01 '11 at 05:39
  • Yes, also be sure you put `#import "MusclesTableViewController.h"` inside `RoutineDayTableViewController.h` because `MusclesTableViewController.h` is where the protocol is defined. – Andrew Nov 01 '11 at 05:42
  • I did that but it still isn't working. I'm now getting an error `expected a type` when I do @protocal declaration. –  Nov 01 '11 at 05:45
  • Forward-declare `@class MusclesTableViewController;` before the @protocol since it is before @interface. – Andrew Nov 01 '11 at 05:52
  • Thanks, that worked. But I think you did it backwards. I want to select the cell in MuscleTableVC, which should dismiss itself, and send the data to RoutineDayTableVC. –  Nov 01 '11 at 06:01
  • That is what this is doing. When a row is selected inside the `MusclesTableViewController` instance, it sends a message back to it's delegate (`RoutineDayTableViewController`) called `-musclesTableViewController:didSelectExerciseWithMuscleURL:` passing the URL that was selected in MusclesTableViewController. I've updated the answer with the modal view controller dismissal in the correct place. – Andrew Nov 01 '11 at 06:09
  • Ok, one last thing. I'm selecting the row from `MuscleTableVC's` child table VC, which is called `SpecificExerciseTableViewController`. So do I need to modify the code? Because it has to travel two layers up. –  Nov 01 '11 at 06:13
  • Yes, to go deeper you would need to create a similar protocol such as **SpecificExerciseTableViewControllerDelegate** with a `-specificExerciseTableViewController:didSelectSpecificExercise:` method. This could send the data back to the MuscleTableViewController. If only `RoutineDayTableViewController` needed the selection data from SpecificExerciseTableViewControllerDelegate, you could make `RoutineDayTableViewController` adopt the protocol and set `RoutineDayTableViewController`'s delegate to the `SpecificExerciseTableViewController` instance. – Andrew Nov 01 '11 at 06:24
  • I only need `RoutineDayTableViewController` to get the selection data from `SpecificExerciseTableViewController`. Can I therefore remove all code related to `MusclesTableViewController`? –  Nov 01 '11 at 06:28
  • You can then replace all instances of `MusclesTableViewController` with `SpecificExerciseTableViewController` and change the method names accordingly and it will work like you want, passing data from `SpecificExerciseTableViewController` to `RoutineDayTableViewController`. The one catch is that you need to pass the pointer to `RoutineDayTableViewController` to `MusclesTableViewController` then to `SpecificExerciseTableViewController`. So you will want to put delegate properties on both classes, and just simply assign that variable up the chain as you instantiate new view controllers. – Andrew Nov 01 '11 at 06:53
  • Thanks Andrew, I tried that but I'm getting a bit lost in the process because its 3 separate classes. If you get a chance, can you update your answer accordingly? Then I can mark it as accepted. Thanks! –  Nov 01 '11 at 07:03
  • I updated my answer accordingly. With a multiple view controller depth scenario the `MuscleTableViewController` now has a `delegate` property that is used pass along the pointer to the instance of RoutineDayTableViewController -> MuscleTableViewController -> SpecificExerciseTableViewController. – Andrew Nov 01 '11 at 07:41
  • Thanks. Everything works except `[routineDayTableViewController addExercise];`. It is not recognizing `routineDayTableViewController` in 'RoutineDayTableViewController.m'. Should it just be `self`? –  Nov 01 '11 at 07:52
  • One absolutely last thing. Do the names of objects in the delegate method have to match the delegate class's properties? I want the delegate method to pass the following objects with these names: `self.selectedExercise, self.muscleName, self.muscleURL`. Those are the names of the properties in `RoutineDayTableViewController`. I have the same named properties in `SpecificExerciseTableViewController`. –  Nov 01 '11 at 08:15
  • The property names in the class instances do not need to match between classes. You can change the delegate method prototype to include any additional parameters you want, just make sure you update the code everywhere that method prototype is used, including the @selector() check. E.g. `- (void)specificExerciseTableViewController:(SpecificExerciseTableViewController *)specificExerciseTableViewController didSelectSpecificExerciseWithURL:(NSURL *)exerciseURL muscleName:(NSString *)muscleName muscleURL:(NSURL *)muscleURL;` then update the rest of the code to reflect the new method name. – Andrew Nov 01 '11 at 08:44
  • Hi Andrew, everyhting is working fine except the app crashes when `addExercise` is called. I think the delegate is fine, but the values aren't getting passed because they're nil when I log them in the `addExercise` method. I'm adding method to the question. –  Nov 02 '11 at 06:09