Best way to pass data from Child Modal VC to the Parent View Controller?
Solution 1
I would suggest, as iPatel did, to use delegation to solve your problem. The relationship between the parent view controller and the login view controller makes this pattern appropriate. When one object creates another in order to fulfill a specific responsibility, one should consider delegation as a way to have the created object communicate with the creator. A particularly compelling reason to choose delegation would be if the task to be accomplished potentially has multiple steps that require a high level of interaction between objects. You can look at the NSURLConnectionDelegate
protocol as an illustration of this. Connecting to a URL is a complex task, involving stages such as processing responses, meeting authentication challenges, saving downloaded data, and handling errors, the connection and the delegate handle this together over the lifetime of the connection.
As you've probably noticed, in Objective-C protocols are used to achieve delegation without tightly coupling the created object (in this case your login view controller) to the object that created it (the parent view controller). The login view controller can then interact with any object that can receive the messages defined in its protocol, rather than rely on any particular class implementation. Tomorrow, if you receive a requirement to allow any view controller to show the login view, the login view controller wouldn't need to change. Your other view controllers can implement its delegate protocol, create and present the login view, and assign themselves as delegates without the login view controller ever knowing of their existence.
Some delegation examples you'll find on Stack Overflow may be very confusing and very un-like what's found in the built-in frameworks. One must pick the names and interfaces of the protocols carefully, as well as the responsibilities assigned to each object, so that code reuse is maximized and the objective of the code is achieved.
You should first take a look at the many delegate protocols within the built-in frameworks for an idea of what the relationship looks like when expressed in code. Here is another small example, based on your login use case. I hope you will find that the purpose of the delegation is clear and that the roles and responsibilities of the objects involved are clear and expressed through their names within the code.
First, let's look at the LoginViewController's delegate protocol:
#import <UIKit/UIKit.h>
@protocol LoginViewControllerDelegate;
@interface LoginViewController : UIViewController
// We choose a name here that expresses what object is doing the delegating
@property (nonatomic, weak) id<LoginViewControllerDelegate> delegate;
@end
@protocol LoginViewControllerDelegate <NSObject>
// The methods declared here are all optional
@optional
// We name the methods here in a way that explains what the purpose of each message is
// Each takes a LoginViewController as the first argument, allowing one object to serve
// as the delegate of many LoginViewControllers
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc;
- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error;
- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc;
- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc;
- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc;
@end
The login controller can communicate a number of events to its delegate, as well as ask its delegate for information used to customize its behavior. It communicates events to the delegate in its implementation as part of its response to user actions:
#import "LoginViewController.h"
@interface LoginViewController ()
@property (weak, nonatomic) IBOutlet UIButton *anonSigninButton;
@end
@implementation LoginViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Here we ask the delegate for information used to layout the view
BOOL anonymousLoginAllowed = NO;
// All our protocol methods are @optional, so we must check they are actually implemented before calling.
if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) {
// self is passed as the LoginViewController argument to the delegate methods
// in this way our delegate can serve as the delegate of multiple login view controllers, if needed
anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self];
}
self.anonSigninButton.hidden = !anonymousLoginAllowed;
}
- (IBAction)loginButtonAction:(UIButton *)sender
{
// We're preteneding our password is always bad. So we assume login succeeds when allowed anonmously
BOOL loginSuccess = [self isAnonymousLoginEnabled];
NSError *loginError = [self isAnonymousLoginEnabled] ? nil : [NSError errorWithDomain:@"domain" code:0 userInfo:nil];
// Fake concurrency
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Notify delegate of failure or success
if (loginSuccess) {
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidLoginSuccessfully:)]) {
[self.delegate loginViewControllerDidLoginSuccessfully:self];
}
}
else {
if ([self.delegate respondsToSelector:@selector(loginViewController:didFailWithError:)]) {
[self.delegate loginViewController:self didFailWithError:loginError];
}
}
});
}
- (IBAction)forgotPasswordButtonAction:(id)sender
{
// Notify delegate to handle forgotten password request.
if ([self.delegate respondsToSelector:@selector(loginViewControllerDidReceivePasswordResetRequest:)]) {
[self.delegate loginViewControllerDidReceivePasswordResetRequest:self];
}
}
- (IBAction)signupButtonAction:(id)sender
{
// Notify delegate to handle signup request.
if ([self.delegate respondsToSelector:@selector(loginViewControllerDiDReceiveSignupRequest:)]) {
[self.delegate loginViewControllerDiDReceiveSignupRequest:self];
}
}
- (BOOL)isAnonymousLoginEnabled
{
BOOL anonymousLoginAllowed = NO;
if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) {
anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self];
}
return anonymousLoginAllowed;
}
@end
The main view controller instantiates and presents a login view controller, and handles its delegate messages:
#import "MainViewController.h"
#import "LoginViewController.h"
#define LOGGED_IN NO
@interface MainViewController () <LoginViewControllerDelegate>
@end
@implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Fake loading time to show the modal cleanly
if (!LOGGED_IN) {
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Create a login view controller, assign its delegate, and present it
LoginViewController *lvc = [[LoginViewController alloc] init];
lvc.delegate = self;
[self presentViewController:lvc animated:YES completion:^{
NSLog(@"modal completion finished.");
}];
});
}
}
#pragma mark - LoginViewControllerDelegate
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc
{
NSLog(@"Login VC delegate - Login success!");
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error
{
// Maybe show an alert...
// UIAlertView *alert = ...
}
- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc
{
// Take the user to safari to reset password maybe
NSLog(@"Login VC delegate - password reset!");
}
- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc
{
// Take the user to safari to open signup form maybe
NSLog(@"Login VC delegate - signup requested!");
}
- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc
{
return YES;
}
@end
Logging in can be a complex, interactive process in some ways, so I do recommend you seriously consider using delegation instead of notifications. However, one thing that may be problematic is that delegates are necessarily only a single object. If you need to have multiple, disparate objects know about the login view controller's progress and stae, then you may need to use notifications. Especially if the login process can be constrained to be very simple, in a way that does not require any interaction beyond passing one-way messages and data, then notifications can become a viable option. You can pass arbitrary variables in a notification back inside the userInfo
property which is an NSDictionary
of whatever you decide to stuff in it. Notifications can impact performance but I understand that only happens nowadays when observers number in the hundreds. Even still, it is not the most natural fit in my mind, as you have the parent object (that more or less controls the lifetime of the child) asking a third party object for updates from the child object.
Solution 2
You can get it by using Protocol, it is Best way.
I will give you the Basic Idea for how to create a Protocol
Also, read this question: How do I create delegates in Objective-C?
Following code give you the basic idea for Protocol, here in below code you can get Button title from MasterViewController
to DetailViewController
.
#DetailViewController.h
#import <UIKit/UIKit.h>
@protocol MasterDelegate <NSObject>
-(void) getButtonTitile:(NSString *)btnTitle;
@end
@interface DetailViewController : MasterViewController
@property (nonatomic, assign) id<MasterDelegate> customDelegate;
#DetailViewController.m
if([self.customDelegate respondsToSelector:@selector(getButtonTitile:)])
{
[self.customDelegate getButtonTitile:button.currentTitle];
}
#MasterViewController.m
create obj of DetailViewController
DetailViewController *obj = [[DetailViewController alloc] init];
obj.customDelegate = self;
[self.navigationController pushViewController:reportTypeVC animated:YES];
and add delegate method in MasterViewController.m for get button title.
#pragma mark -
#pragma mark - Custom Delegate Method
-(void) getButtonTitile:(NSString *)btnTitle;
{
NSLog(@"%@", btnTitle);
}
Alan
Updated on July 18, 2022Comments
-
Alan almost 2 years
What is the best way to pass data from a child modal view to the parent view controller?
I have a Child Modal Login Screen on my iPad app that I want to pass back user information to the parent Split View Controller.
I am thinking of using NSNotification, but I am not sure if this is the easiest/most efficient way to pass data back to the parent.
Thanks! Alan
-
Mike M about 11 yearsI use NSNotifications a lot for this purpose. I'm not a fan of protocols in this case because the child would need to know about the parent's methods but that's a design preference thing...
-
-
Alan about 11 yearsgreat! I will give this a try and get back to you. Thank you.
-
Alan about 11 yearsout of curiosity why is this method better than NSNotification?
-
Alan about 11 yearswow I really appreciate you taking the time in giving me such a thorough explanation. I now understand a lot more about creating delegates and when to use them. Thank you very much!
-
Alan about 11 yearsI noticed that all your delegates returns itself. Do all Delegates generally return itself?
-
Carl Veazey about 11 years@Alan, they don't actually return
self
, they are passingself
as an argument to the methods. You'll often see that delegate protocols have a parameter for the delegating object in their methods. Look atUITableViewDelegate
for instance, the first argument of every method is aUITableView
. Delegating objects pass themselves in the delegate method calls so that a delegate can serve as the delegate for multiple such objects and have a way to distinguish between who is calling the method. That's a great question by the way and I should add that to my answer explicitly. -
Alan about 11 yearsAhh Great! Thank you Carl!
-
NetDeveloper over 10 yearsHow can I use the above If I have to go through multiple view controllers to get the user input in modal view, where modal view is UINavigationController?
-
Carl Veazey over 10 years@Jignesh if the modal is a
UINavigationController
then use the nav controller's root view controller as the delegating class.