Just when I thought I had my head around how delegates work, it seems I am missing something.
I've setup a delegate like so:
ViewControllerA.h
#protocol LoginDelegate <NSObject>
-(void)userLoginSuccessful;
#end
#interface BBLoginViewController : BBBaseViewController
#property (weak, nonatomic) id <LoginDelegate> delegate;
#end
ViewControllerA.m
-(void)someMethod
{
if ([self.delegate respondsToSelector:#selector(userLoginSuccessful)]){
[self.delegate userLoginSuccessful];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
ViewControllerB.m
#import "ViewControllerA.h"
#interface ViewControllerB () <UITableViewDataSource, UITableViewDelegate, LoginDelegate>
-(void)viewWillAppear:(BOOL)animated
{
ViewControllerA *loginViewController = [[ViewControllerA alloc]init];
loginViewController.delegate = self;
}
#pragma mark - Login Delegate
-(void) userLoginSuccessful
{
NSLog (#"Delegate fired!");
}
After all this my delegate method does not fire. What am I doing wrong? I looked at this SO answer and when checking the delegate is not nil - it is not. Its set to ViewControllerB.
This code:
-(void)viewWillAppear:(BOOL)animated
{
ViewControllerA *loginViewController = [[ViewControllerA alloc]init];
loginViewController.delegate = self;
}
Will create a ViewControllerA object and then (almost) immediately destroy it and is not what you intended.
You probably want to be setting the delegate in the prepareForSegue: method anyway...
Set the delgate before presenting ViewControllerB. The below code is not required anymore
-(void)viewWillAppear:(BOOL)animated
{
ViewControllerA *loginViewController = [[ViewControllerA alloc]init];
loginViewController.delegate = self;
}
Dont do like that. It will give you unexpected results. Remember always set the delegate before moving to the next view. If you are presenting modally just before presentViewController of if you are using segue in prepareForSegue
Related
I'd like to trigger a message from viewController to another viewController.
And I coded like below, but viewController didn't call delegate.
I want to call delegate without stroyboard. I just want to send a message to another viewController.
viewController2.h
#protocol ViewController2Delegate;
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id<ViewController2Delegate> delegate;
#end
#protocol ViewController2Delegate <NSObject>
- (void)showSomethingByDelegate;
#end
viewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#end
#implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)buttonPressed:(id)sender {
[self.delegate showSomethingByDelegate];
}
#end
viewController.m
#import "ViewController.h"
#import "ViewController2.h"
#interface ViewController ()
<ViewController2Delegate>
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ViewController2 *vc2 = [[ViewController2 alloc] init];
vc2.delegate = self;
}
// Below method was not called by delegate.
- (void)showSomethingByDelegate {
NSLog(#"Button Was Pressed!!!");
}
#end
well let me show an example more simple.
File: firstVC.h
/* This define the protocol object,
you can write methods required or optional the diference is when
the protocol is used in a class, xcode show you a yellow warning with required methods
like UITableViewDelegate, UITextFieldDelegate... */
#protocol firstVCDelegate <NSObject>
#required
- (void)didMessageFromOtherViewController: (NSString *)messageStr;
#optional
- (void)didOtherMessageNotRequired: (NSString *)messageStr;
#end
/* This is the definition of first UIViewController */
#interface firstViewController : UIViewController
#end
/* This is the definition of second UIViewController object with
a property that is our protocol 'firstVCDelegate' */
#interface secondViewController : UIViewController
#property (nonatomic, weak) id <firstVCDelegate> firstVCDelegate;
- (void)executeDelegateProcess;
#end
File: firstVC.m
#import "firstVC.h"
#pragma mark - First UIViewController with delegate
#interface firstViewController() <firstVCDelegate>
#end
#implementation firstViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Creating the 'secondViewController' object with delegate on this class
secondViewController *svc = [secondViewController new];
// Assign the delegate class
[svc setFirstVCDelegate: self];
// Run the delegate logic
[svc executeDelegateProcess];
}
- (void)didMessageFromOtherViewController:(NSString *)messageStr
{
// Receiving the message from the instance of our protocol in the 'secondViewController' class
NSLog(#"MESSAGE #1: %#", messageStr);
}
- (void)didOtherMessageNotRequired:(NSString *)messageStr
{
// Receiving the message in optional method
NSLog(#"MESSAGE #2: %#", messageStr);
}
#end
#pragma mark - Second UIViewController
#implementation secondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)setFirstVCDelegate:(id<firstVCDelegate>)firstVCDelegate
{
if (firstVCDelegate)
_firstVCDelegate = firstVCDelegate;
}
- (void)executeDelegateProcess
{
// This method is only for demo
// You can execute your delegate in the way you need to use
if (_firstVCDelegate) {
[_firstVCDelegate didMessageFromOtherViewController: #"Hello world, using the required method from secondViewController class"];
[_firstVCDelegate didOtherMessageNotRequired: #"Hello code using the optional method"];
}
}
#end
In your appDelegate.m in the method didFinishLaunchingWithOptions you can put this, and you need to #import "firstVC.h"
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.autoresizesSubviews = YES;
[self.window makeKeyAndVisible];
[_window setRootViewController: [firstViewController new]];
Execute and see two logs messages, I hope I've helped :)
ViewController2 *vc2 = [[ViewController2 alloc] init];
This line of code is not doing what you think it is, this is creating a NEW instance of ViewController2 and storing it inside the variable vc2. At no point (based on your code) is this variable added to the navigation stack and displayed on screen.
Edit
Also as noticed by Holex, your vc2 variable will be removed from memory after the viewDidLoad is finished as you are not holding onto a reference of it. You would either need to create a property to hold onto it, or push it onto the navigation stack for the navigationController to hold onto it.
There are multiple ways to solve this issue:
Instead of creating a new viewController2, find a reference to the one already displayed on the screen (if it is on the screen).
If its not on the navigation stack, add vc2 to the navigation stack [self.navigationController pushViewController:vc2 animated:YES]; (assuming you have a navigation controller).
If not on the stack, and not intending to use a navigationController, present vc2 modally, such as [self presentViewController:vc2 animated:YES completion:nil];
I have three UIViewcontroller, namely ViewController, BViewController and CViewController, ViewController is RootViewController from UINavigationController,
I'm pushing from ViewController -> BViewController -> CViewController, through button action.
I have declared protocol in CViewController. This protocol method is working fine for BViewController, but I don't understand how to make work its delegate method in ViewController.
I had created an object of CViewController in View Controller, then declared self for that delegate, though its not working, Below is my code. Please help me. Where I'm doing wrong here !
My Root View Controller, namely ViewController
#import "ViewController.h"
#import "BViewController.h"
#import "CViewController.h"
#interface ViewController ()<testDelegates>
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CViewController *cVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CViewController"];
cVC.mydelegate = self;
}
- (IBAction)nextAction:(id)sender {
BViewController *bViewC = [self.storyboard instantiateViewControllerWithIdentifier:#"BViewController"];
[self.navigationController pushViewController:bViewC animated:YES];
}
-(void)TestMethod{
NSLog(#" Its NOT Working");
}
My Second View Controller, Namely BViewController
#import "BViewController.h"
#import "CViewController.h"
#interface BViewController ()<testDelegates>
#end
#implementation BViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)bNextAction:(id)sender {
CViewController *cVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CViewController"];
// cVC.mydelegate = self;
[self.navigationController pushViewController:cVC animated:YES];
}
-(void)TestMethod{
NSLog(#" Its Working If I uncomment to cVC.mydelegate = self");
}
And my third View Controller, Where i Declared a protocol in .h file, namely CViewController
#import <UIKit/UIKit.h>
#protocol testDelegates <NSObject>
-(void)TestMethod;
#end
#interface CViewController : UIViewController
#property (weak) id <testDelegates> mydelegate;
#end
And in .m file
#import "CViewController.h"
#interface CViewController ()
#end
#implementation CViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)cNextAction:(id)sender {
[self.mydelegate TestMethod];
}
You create class and this class destroying after you leave viewDidLoad method.
- (void)viewDidLoad {
[super viewDidLoad];
CViewController *cVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CViewController"];
cVC.mydelegate = self;
}
As I understand you need to perform some methods in the ViewController from the CViewController.
You can pass viewController as delegate from the BViewController and them past it to the CViewController.
Or you can use code like this:
- (IBAction)cNextAction:(id)sender {
id< testDelegates > delegate = self.navigationController.viewControllers[0];
if([delegate conformsToProtocol:#protocol(testDelegates)])
{
[delegate TestMethod];
}
}
This is no best way but it's will be work.
Try this
self.delegate = self.navigationController.viewControllers[0];
You need to declare a another Protocol into BViewController class.
Please follow below steps.
Step 1: BViewController.h
#protocol testDelegates2 <NSObject>
-(void)TestMethod2;
#interface BViewController : UIViewController
#property (weak) id <testDelegates> mydelegate;
#end
#implementation CViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)cNextAction:(id)sender {
[self.mydelegate TestMethod2];
}
Step 2. In ViewController class conform Protocol
#interface ViewController ()<testDelegates>
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CViewController *cVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CViewController"];
cVC.mydelegate = self;
}
- (IBAction)nextAction:(id)sender {
BViewController *bViewC = [self.storyboard instantiateViewControllerWithIdentifier:#"BViewController"];
cVC.mydelegate = self; //important line
[self.navigationController pushViewController:bViewC animated:YES];
}
-(void)TestMethod2{
NSLog(#"Final Output");
}
If Your are trying to set Delegate of CViewController to ViewController then
try putting
self.mydelegate = self.navigationController.viewControllers[0]
on viewDidLoad method of CViewController.
Else you are Trying to make ViewController And BViewController both be Delegate of CViewController then this is not possible like this, as Delegate of a Object Can only be unique. This means the delegate is simply a variable which holds reference of object and as you know value to a variable can be unique at a instance of time.
So If you are willing to do some task on both Viewcontroller and BViewController then use notification pattern. See apple doc here
I've done this delegation pattern a million times. On the million and one-th time I'm getting a nil delegate:
CatViewController *vc = [[UIStoryboard storyboardWithName:#"Cat" bundle:nil] instantiateInitialViewController];
vc.delegate = self;
[self presentViewController:vc animated:YES completion:^{
DDLogWarn(#"[%# %#] delegate: %#", THIS_FILE, THIS_METHOD, vc.delegate); //here the delegate is valid
}];
#protocol CatViewControllerDelegate;
#interface CatViewController : UIViewController
#property (weak, nonatomic) id <CatViewControllerDelegate> delegate;
#end
#protocol CatViewControllerDelegate <NSObject>
#required
- (void)catViewController:(CatViewController*)catVC didFinishWithSuccess:(BOOL)success;
#end
However, in viewDidLoad of CatViewController, self.delegate is already nil, and of course is nil when I try to do this:
[self.delegate catViewController:self didFinishWithSuccess:YES];
Why is the catViewController's delegate becoming nil?
The problem was that the CatViewController was embedded in a Navigation Controller in the Storyboard. Therefore instantiateInitialViewController was not returning a CatViewController, although everything appeared fine on screen. The fix:
UINavigationController *navVC = [[UIStoryboard storyboardWithName:#"Cat" bundle:nil] instantiateInitialViewController];
CatViewController *catVC = navVC.viewControllers[0];
catVC.delegate = self;
You have:
vc.delegate = self;
Whatever self is, it is being deleted by the time your new view controller's viewDidLoad gets called. To test this theory, put a dealloc method in that class and put a breakpoint in it.
Did you try to declare the delegate like this instead?
#property (unsafe_unretained, nonatomic) id <CatViewControllerDelegate> delegate;
I have the delegate working as the data is being passed from the modal to the presenting view controller. But the presenting view controller isn't showing the data it receives from the modal. I looked at other posts and they say to use the delegate/protocol method, but don't explain how/why the presenting VC refreshes. I'm assuming my delegate is setup incorrectly. Otherwise, what is the method to refresh the data? I've checked and the viewWillAppear and viewDidAppear doesn't get called.
SCCustomerDetailVC.h (Presenting VC)
#import "SCCustomersVC.h"
#interface SCCustomerDetailVC : UIViewController <SCCustomersVCDelegate>
#property (atomic, strong) SCCustomer *customer;
#property (strong, nonatomic) IBOutlet UIButton *changeCustomerButton;
- (IBAction)changeCustomerButtonPress:(UIButton *)sender;
#end
SCCustomerDetailVC.m (Presenting VC)
- (IBAction)changeCustomerButtonPress:(UIButton *)sender
{
UINavigationController *customersNC = [self.storyboard instantiateViewControllerWithIdentifier:#"customersNC"];
SCCustomersVC *customersVC = (SCCustomersVC *)customersNC.topViewController;
customersVC.delegate = self;
[self presentViewController:customersNC animated:YES completion:nil];
}
//Protocol methods
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:nil];
}
SCCustomersVC.h (Modal VC)
#import "SCCustomersVCDelegate.h"
#class SCCustomerDetailVC;
#interface SCCustomersVC : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
#property (weak, nonatomic) id <SCCustomersVCDelegate> delegate;
#end
SCCustomersVC.m (Modal VC)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SCCustomer *customer = [self customerAtIndexPath:indexPath];
[self.delegate passCustomer:customer];
}
SCCustomersVCDelegate.h
#class SCCustomer;
#protocol SCCustomersVCDelegate <NSObject>
#optional
- (void)passCustomer:(SCCustomer *)customer;
#end
I think you're nearly there. EDIT - just learned here that viewWillAppear behavior is different in iOS>5. You still want view will appear to update your view with your model state, since it needs to do that on initial presentation.
And it's fine to call it from either your modal vc or from within the delegate method. So add code to viewWillAppear that updates the view with the view controller's model state...
// SCCustomerDetailVC.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// making up an IBOutlet called someLabel
// making up a model method (description) that returns a string representing your model
self.someLabel.text = [self.customer description];
}
Then either from the presented vc or the delegate, call viewViewWillAppear:
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
[self viewWillAppear:YES];
[self dismissViewControllerAnimated:YES completion:^{}];
}
You should reload your UI after setting the "customer"
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:^{
// reload your UI here or call a method which will reload your ui
// e.g. for tableView it will be [tableView reload];
}];
}
In a view, let's call it firstView I created a secondView as follows and pushed it if certain thing happened in the firstView:
SecondViewController *secondVC = [[secondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
[self.navigationController pushViewController:secondVC animated:YES];
[secondVC release];
Now when I'm in the secondView if let say a button is pressed I want to go back to firstView and also pass back a value from secondView to the firstView (let say an integer value of a textfield from secondView to the firstView).
Here is what I tried:
#protocol SecondViewControllerDelegate;
#import <UIKit/UIKit.h>
#import "firstViewController.h"
#interface SecondViewController : UIViewController <UITextFieldDelegate>
{
UITextField *xInput;
id <SecondViewControllerDelegate> delegate;
}
- (IBAction)useXPressed:(UIButton *)sender;
#property (assign) id <SecondViewControllerDelegate> delegate;
#property (retain) IBOutlet UITextField *xInput;
#end
#protocol SecondViewControllerDelegate
- (void)secondViewController:(SecondViewController *)sender xValue:(int)value;
#end
And in the m file
- (IBAction)useXPressed:(UIButton *)sender
{
[self.delegate secondViewController:self xValue:1234]; // 1234 is just for test
}
And then in the firstView I did:
#import "SecondViewController.h"
#interface FirstViewController : UITableViewController <SecondViewControllerDelegate> {
}
#end
And implemented:
- (void) secondViewController:(SecondViewController *)sender xValue:(int)value
{
[self.navigationController popViewControllerAnimated:YES];
}
Now, the problem is for one in FirstViewController I get the warning that "No definition of protocol "SecondViewControllerDelegate" is found, and for two the delegate method (last piece of code above) does not get invoked at all. Can somebody please tell me what's wrong?
After this line
SecondViewController *secondVC = [[secondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
Add
secondVC.delegate = self;
Also instead of
- (void) secondViewController:(SecondViewController *)sender xValue:(int)value
{
[self.navigationController popViewControllerAnimated:YES];
}
You should use
- (void) secondViewController:(SecondViewController *)sender xValue:(int)value
{
[sender popViewControllerAnimated:YES];
}
In FirstViewController .h file :
#import "SecondViewController.h"
#interface FirstViewController : UITableViewController <SecondViewControllerDelegate> {
SecondViewController *secondViewController;
}
#end
In implementation file , where you init SecondViewController instance next line assign self to delegate property :
secondViewController.delegate = self;
Next define delegate method :
- (void)secondViewController:(SecondViewController *)sender xValue:(int)value
{
NSLog ("This is a Second View Controller with value %i",value)
}
For problem 1: The #protocol definition for SecondViewControllerDelegate looks like it's in secondViewController.h; are you sure this file is imported in firstViewController.h? Otherwise it won't know about the protocol.
Problem 2: It might be totally unrelated to problem 1. Are you sure the action is hooked up properly? Can you put a NSLog() call in your useXPressed: to make sure that method is actually getting called when you expect it to?