How can I detect the dismissal of a modal view controller in the parent view controller?

67,532

Solution 1

This answer was rewritten/expanded to explain the 3 most important approaches (@galambalazs)

1. Blocks

The simplest approach is using a callback block. This is good if you only have one listener (the parent view controller) interested in the dismissal. You may even pass some data with the event.

In MainViewController.m

SecondViewController* svc = [[SecondViewController alloc] init];
svc.didDismiss = ^(NSString *data) {
    // this method gets called in MainVC when your SecondVC is dismissed
    NSLog(@"Dismissed SecondViewController");
};
[self presentViewController:svc animated:YES completion:nil];

In SecondViewController.h

@interface MainViewController : UIViewController
    @property (nonatomic, copy) void (^didDismiss)(NSString *data);
    // ... other properties
@end

In SecondViewController.m

- (IBAction)close:(id)sender 
{
    [self dismissViewControllerAnimated:YES completion:nil];

    if (self.didDismiss) 
        self.didDismiss(@"some extra data");
}

2. Delegation

Delegation is the recommended pattern by Apple:

Dismissing a Presented View Controller

If the presented view controller must return data to the presenting view controller, use the delegation design pattern to facilitate the transfer. Delegation makes it easier to reuse view controllers in different parts of your app. With delegation, the presented view controller stores a reference to a delegate object that implements methods from a formal protocol. As it gathers results, the presented view controller calls those methods on its delegate. In a typical implementation, the presenting view controller makes itself the delegate of its presented view controller.

MainViewController

In MainViewController.h

@interface MainViewController : UIViewController <SecondViewControllerDelegate>
    - (void)didDismissViewController:(UIViewController*)vc;
    // ... properties
@end

Somewhere in MainViewController.m (presenting)

SecondViewController* svc = [[SecondViewController alloc] init];
svc.delegate = self;
[self presentViewController:svc animated:YES completion:nil];

Somewhere else in MainViewController.m (being told about the dismissal)

- (void)didDismissViewController:(UIViewController*)vc
{
    // this method gets called in MainVC when your SecondVC is dismissed
    NSLog(@"Dismissed SecondViewController");
}

SecondViewController

In SecondViewController.h

@protocol SecondViewControllerDelegate <NSObject>
- (void)didDismissViewController:(UIViewController*)vc;
@end

@interface SecondViewController : UIViewController
@property (nonatomic, weak) id<SecondViewControllerDelegate> delegate;
// ... other properties
@end

Somewhere in SecondViewController.m

[self.delegate didDismissViewController:self];
[self dismissViewControllerAnimated:YES completion:nil];

(note: the protocol with didDismissViewController: method could be reused throughout your app)


3. Notifications

Another solution is sending an NSNotification. This is a valid approach as well, it might be easier than delegation in case you only want to notify about the dismissal without passing much data. But it's main use case is when you want multiple listeners for the dismissal event (other than just the parent view controller).

But make sure to always remove yourself from NSNotificationCentre after you are done! Otherwise you risk of crashing by being called for notifications even after you are deallocated. [editor's note]

In MainViewController.m

- (IBAction)showSecondViewController:(id)sender 
{
    SecondViewController *secondVC = [[SecondViewController alloc] init];
    [self presentViewController:secondVC animated:YES completion:nil];

    // Set self to listen for the message "SecondViewControllerDismissed"
    // and run a method when this message is detected
    [[NSNotificationCenter defaultCenter] 
     addObserver:self
     selector:@selector(didDismissSecondViewController)
     name:@"SecondViewControllerDismissed"
     object:nil];
}

- (void)dealloc
{
    // simply unsubscribe from *all* notifications upon being deallocated
    [[NSNotificationCenter defaultCenter] removeObserver:self];
} 

- (void)didDismissSecondViewController 
{
    // this method gets called in MainVC when your SecondVC is dismissed
    NSLog(@"Dismissed SecondViewController");
}

In SecondViewController.m

- (IBAction)close:(id)sender 
{
    [self dismissViewControllerAnimated:YES completion:nil];

    // This sends a message through the NSNotificationCenter 
    // to any listeners for "SecondViewControllerDismissed"
    [[NSNotificationCenter defaultCenter] 
     postNotificationName:@"SecondViewControllerDismissed" 
     object:nil userInfo:nil];
}

Hope this helps!

Solution 2

The modal view should tell its parent to dismiss it, then the parent will know because it is responsible for doing the dismissing.

An example of this can be seen if you create a new project and choose the Utility Application template.

Share:
67,532
objc-obsessive
Author by

objc-obsessive

Updated on August 03, 2020

Comments

  • objc-obsessive
    objc-obsessive over 3 years

    Possible Duplicate:
    Call Function in Underlying ViewController as Modal View Controller is Dismissed

    I've tried almost everything. Here's what I've tried:

    -(void)viewWillAppear:(BOOL)animated
    {
    
    NSLog(@"Test");
    
    }
    
    -(void)viewDidAppear:(BOOL)animated
    {
    
    NSLog(@"Test");
    
    }
    
    -(void)viewDidLoad
    {
    
    NSLog(@"Test");
    
    }
    

    Why are none of these working in my parent view controller when the modal view controller is dismissed? How can I get this to work?

  • objc-obsessive
    objc-obsessive about 12 years
    I did [self.parentViewController dismissModalViewControllerAnimated:YES]; but -(void)viewWillAppear:(BOOL)animated still didn't detect it
  • Paul.s
    Paul.s about 12 years
    No but you know the modal has been removed, so what ever code you have running in viewWillAppear: that you wanted run when the view appears and when the modal is removed should be placed in it's own method which is called from both places.
  • Paul.s
    Paul.s about 12 years
    Also you must not have looked at the template code provided to Apple that I pointed you to, as they use delegate not simply self.parentViewController dismiss...
  • Paul.s
    Paul.s about 12 years
    It kind of seems like 'NSNotificationCenter' is overkill here, only one object needs to know when the viewController is dismissed. I would say it adds additional maintainence overhead due the over complication..
  • moxy
    moxy about 11 years
    Paul.s is right, check out Apple's documentation : developer.apple.com/library/ios/#featuredarticles/…
  • moxy
    moxy about 11 years
    here is the link to the apple's documentation that support what you said : developer.apple.com/library/ios/#featuredarticles/…
  • Saren Inden
    Saren Inden about 9 years
    For a simple callback like this you should just add a delegate that calls the parent view controller. Notifications are meant to go to multiple receivers like login status changed.
  • Tyler
    Tyler almost 9 years
    If you use this method, be sure to remove self after didDismissSecondViewController is called: [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SecondViewControllerDismissed" object:nil];
  • AsimRazaKhan
    AsimRazaKhan over 6 years
    my delegate is not getting called!
  • Dani
    Dani about 6 years
    @moxy page not found