1
votes

I was wondering how I would be able to update a view controller property of the presenting view controller from a method called inside the modal view controller? Right now, the only way I could think of is using NSNotificationCenter, and while this works for Dictionary items, I couldn't figure out how to use it for a custom object.

For example, my presenting view controller HomewViewController has a Parse PFObject called homeSelections, which the could be updated in the modally presented ModalViewController's property newSelections (and also a PFObject) . After the user makes her selections, I would like HomeViewController's homeSelections to also have the latest data passed from the modal view controller.

Help is appreciated - thanks.

Update 1: here is what I have done now (note that I am using a stripped down example to test things out)

In ViewController (this is the parent/presenting view controller)

@interface ViewController ()

@property (strong, nonatomic) NSArray *totalRamen;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.totalRamen = @[@"ramen1", @"ramen2", @"ramen3", @"moot"];

}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"self.totalRamen: %@", self.totalRamen);
    NSLog(@"Done");
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"showModal"]){
        ModalViewController *destinationVC = (ModalViewController *)segue.destinationViewController;
        destinationVC.passedRamen = self.totalRamen;
    }
}

- (IBAction)showModalAction:(UIButton *)sender
{
    ModalViewController *destinaionViewController = [[ModalViewController alloc] init];
    destinaionViewController.selectionCallback = ^(id selectedItem) {
        self.totalRamen = (NSArray *)selectedItem;
        NSLog(@"self.totalRAmen %@", self.totalRamen);
        NSLog(@"done");
    };
    [self performSegueWithIdentifier:@"showModal" sender:self];
}

In ModalViewController (this is the presented/modal view controller)

@interface ModalViewController : UIViewController

@property (strong, nonatomic) NSArray *passedRamen;
- (IBAction)dismissModal:(UIButton *)sender;

typedef void(^CallbackBlock)(id value);
@property (nonatomic, copy) CallbackBlock selectionCallback;

@end

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    NSLog(@"Passed ramen %@", self.passedRamen);
    NSLog(@"Done");

    self.passedRamen = @[@"moot is awesome"];
    NSLog(@"new ramen: %@", self.passedRamen);
    NSLog(@"%@", self.selectionCallback); //nil here

    //call back
    CallbackBlock selectionCallback = self.selectionCallback;
    if (selectionCallback){
        selectionCallback(self.passedRamen); //I want to send the newly updated self.passedRamen back
    } else {
        NSLog(@"No show"); //Means if isn't called
    }
}

- (IBAction)dismissModal:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
}
2
Pass a callback block into the presented view controller or use delegation. Blocks are the cleaner approach.CrimsonChris
Thanks for commenting @CrimsonChris - is there a code sample that I could check out about how to do this? I feel like this is an important concept and I want to make sure to get it right when I implement this. Thanks!daspianist
Thanks for point it out. I am a bit confused: I thought I was following the suggestion of PresentedViewController *vc = [[PresentedViewController alloc] init];. and using the vc.selectionCallback in the next line? Based on the code snippet, should I be calling the callback another way?daspianist
You are setting the callback correctly, just on the wrong instance of your modal view controller.CrimsonChris

2 Answers

4
votes

Pass a callback block into the presented view controller. This way the presented view controller doesn't know anything about the view controller presenting it. Much more flexible because now anybody can present your view controller, they just pass it a block!

PresentingViewController

PresentedViewController *vc = [[PresentedViewController alloc] init]; //or get your existing one
vc.selectionCallback = ^(id selectedItem) {
    //update selected items here
};
//present vc here

PresentedViewController

typedef void(^CallbackBlock)(id value);

@property (nonatomic, copy) CallbackBlock selectionCallback;

- (void)somethingWasSelected:(id)selectedItem {
    CallbackBlock selectionCallback = self.selectionCallback;
    if (selectionCallback) selectionCallback(selectedItem);
}

Beware of retain cycles. This block is being retained by the presented view controller so references to the presented view controller in the block without weakifying it first will create a leak. More info on this can be found here.

0
votes

After the user makes her selections, I would like HomeViewController's homeSelections to also have the latest data passed from the modal view controller.

The easy way is to avoid having ModalViewController update HomeViewController at all. Turn the communication around -- have HomeViewController query ModalViewController and update itself when the modal is dismissed.

HomeViewController already depends on ModalViewController -- it has to know about ModalViewController in order to present it. So there's no harm in having it also know how to read the newSelections property out of ModalViewController at the appropriate time. ModalViewController, on the other hand, has no need to know anything about where its information goes. It doesn't need to know about HomeViewController in order to do its job. If you avoid telling ModalViewController anything about HomeViewController, you can easily use ModalViewController from some other view controller should the need for that ever arise. More importantly, you avoid ever needing to update ModalViewController if HomeViewController changes.

For example, your HomeViewController might look (in part) like this:

- (void)showModalViewController
{
    self.modalViewController = [[ModalViewController alloc] init]; // or otherwise get the modal controller
    [self presentViewController:self.modalViewController
                       animated:YES 
                     completion:];
}

- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
    self.homeSelections = self.modalViewController.newSelections;
    [super dismissViewControllerAnimated:flag completion:completion];
    self.modalViewController = nil;
}