dismissModalViewController AND pass data back

34,406

Solution 1

You need to use delegate protocols... Here's how to do it:

Declare a protocol in your secondViewController's header file. It should look like this:

#import <UIKit/UIKit.h>

@protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
@end


@interface SecondViewController : UIViewController
{
    id myDelegate;  
}

@property (nonatomic, assign) id<SecondDelegate>    myDelegate;

Don't forget to synthesize the myDelegate in your implementation (SecondViewController.m) file:

@synthesize myDelegate;

In your FirstViewController's header file subscribe to the SecondDelegate protocol by doing this:

#import "SecondViewController.h"

@interface FirstViewController:UIViewController <SecondDelegate>

Now when you instantiate SecondViewController in FirstViewController you should do the following:

// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = @"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];

Lastly, in the implementation file for your first view controller (FirstViewController.m) implement the SecondDelegate's method for secondViewControllerDismissed:

- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
    NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}

Now when you're about to dismiss the second view controller you want to invoke the method implemented in the first view controller. This part is simple. All you do is, in your second view controller, add some code before the dismiss code:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];

Delegate protocols are EXTREMELY, EXTREMELY, EXTREMELY useful. It would do you good to familiarize yourself with them :)

NSNotifications are another way to do this, but as a best practice, I prefer using it when I want to communicate across multiple viewControllers or objects. Here's an answer I posted earlier if you're curious about using NSNotifications: Firing events accross multiple viewcontrollers from a thread in the appdelegate

EDIT:

If you want to pass multiple arguments, the code before dismiss looks like this:

if([self.myDelegate respondsToSelector:@selector(secondViewControllerDismissed:argument2:argument3:)])
{
    [self.myDelegate secondViewControllerDismissed:@"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];

This means that your SecondDelegate method implementation inside your firstViewController will now look like:

- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
    NSString thisIsTheDesiredString = stringForFirst;
    NSObject desiredObject1 = inObject1;
    //....and so on
}

Solution 2

I could be way out of place here, but I am starting to much prefer the block syntax to the very verbose delegate/protocol approach. If you make vc2 from vc1, have a property on vc2 that you can set from vc1 that is a block!

@property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);

Then, when something happens in vc2 that you want to tell vc1 about, just execute the block that you defined in vc1!

self.somethingHappenedInVC2(@"Hello!");

This allows you to send data from vc2 back to vc1. Just like magic. IMO, this is a lot easier/cleaner than protocols. Blocks are awesome and need to be embraced as much as possible.

EDIT - Improved example

Let's say we have a mainVC that we want to present a modalVC on top of temporarily to get some input from a user. In order to present that modalVC from mainVC, we need to alloc/init it inside of mainVC. Pretty basic stuff. Well when we make this modalVC object, we can also set a block property on it that allows us to easily communicate between both vc objects. So let's take the example from above and put the follwing property in the .h file of modalVC:

 @property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);  

Then, in our mainVC, after we have alloc/init'd a new modalVC object, you set the block property of modalVC like this:

ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
     NSLog(@"Something was selected in the modalVC, and this is what it was:%@", response);
}

So we are just setting the block property, and defining what happens when that block is executed.

Finally, in our modalVC, we could have a tableViewController that is backed by a dataSource array of strings. Once a row selection is made, we could do something like this:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      NSString *selectedString = self.dataSource[indexPath.row];
      self.somethingHappenedInModalVC(selectedString);
 }

And of course, each time we select a row in modalVC, we are going to get a console output from our NSLog line back in mainVC. Hope that helps!

Solution 3

hmm, look for the notification centre and pass back info in a notification. here is apples take on it - I take this approach personally unless any one has any other suggestions

Solution 4

Define a delegate protocol in the second view controller and make the first one the delegate of the second.

Share:
34,406

Related videos on Youtube

Andrew Davis
Author by

Andrew Davis

iOS developer. SOreadytohelp

Updated on February 24, 2020

Comments

  • Andrew Davis
    Andrew Davis about 4 years

    I have two view controllers, firstViewController and secondViewController. I am using this code to switch to my secondViewController (I am also passing a string to it):

    secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];
    
    second.myString = @"This text is passed from firstViewController!";
    
    second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    
    [self presentModalViewController:second animated:YES];
    
    [second release];
    

    I then use this code in secondViewController to switch back to the firstViewController:

    [self dismissModalViewControllerAnimated:YES];
    

    All of this works fine. My question is, how would I pass data to the firstViewController? I would like to pass a different string into the firstViewController from the secondViewController.

  • theiOSDude
    theiOSDude almost 13 years
    The link actually over-complicates it, all you need is an observer (first View Controller) and send the notif from the second. You can assign selectors to a notification and get the information sent back through the notification also.
  • Michael
    Michael about 11 years
    According to Apple's View Controller Programming Guide for iOS the secondViewController should be dismissed in the presenting view controller, not in the presented one.
  • Sid
    Sid about 11 years
    Sounds like you haven't set the UITableView's delegate. Could you post this as a question, with the code you have and circle back? I might be able to help you.
  • Sid
    Sid about 11 years
    You had missed the "second.myDelegate = self;" line from my answer but someone else caught the problem, so hopefully it's all good now :) Cheers!!
  • Pupillam
    Pupillam over 10 years
    Appretiate that, that you!!
  • Sid
    Sid over 10 years
    @Michael The documentation says that calling dismiss on self forwards the call to the presenting view controller. Also, calling on self is cleaner as that way you don't need to worry about switching between presentingViewController and parentViewController depending on the iOS version you're targeting (5 or before).
  • malaki1974
    malaki1974 about 10 years
    Should this still work when using storyboards? Right now it is not working for me. Just quits with a lldb error. The main diff. I can see is the alloc of the vc is now an instantiated storyboard flow. EDIT And I was presenting before creating the block. Fixed.
  • Sid
    Sid almost 10 years
    I agree with you :) I posted my answer quite a while ago. Now, I switch between using blocks / protocols depending on the usage. Seeing as this thread is still pretty active to this day, I should, at some point, edit my answer to include blocks.
  • Mohit Tomar
    Mohit Tomar almost 10 years
    @sid ... its a great post. really helped.
  • Sid
    Sid over 9 years
    @Resty I do agree; blocks are amazingly useful. I was considering changing this answer to support blocks at some point. However in this case, I left the delegate answer visible for now because it gives us a little more freedom to manipulate objects that could be passed into the modal. I'm just lazy and will update this answer to use blocks soon :)
  • Sukitha Udugamasooriya
    Sukitha Udugamasooriya about 9 years
    This answer needs to be accepted since this brings the most intuitive solution.
  • lozflan
    lozflan about 9 years
    how would you do this exact block thing but using a closure in swift??
  • kygcoleman
    kygcoleman almost 9 years
    Out of the two appropriate answers, this is the best one!
  • ChenSmile
    ChenSmile about 8 years
    @sid thanks bro it works for me but u slightly need to modify. as many things got change. please edit it
  • Sid
    Sid about 8 years
    @Imran thanks for the call-out. I've been meaning to edit my stackoverflow answers for a while :) I'll try my best to update them asap.
  • HixField
    HixField about 8 years
    This is by far my preferred method. In swift this is then accomplished with closures. Much better then delegates and notifications because you dont need to specify protocols or these "ugly" notification constants. If you make the name of the variable in the presented vc that holds the closure nice it can be very intuitive code eg. Vc.didCancel, vc.didFinish... You can set these in the prepareForSegue f the vc that presents it (if you are using segues).
  • Ravi
    Ravi over 7 years
    Woking like a charm.