I've got two view controllers, HomeViewController and AddViewController. HomeViewController is the delegate of AddViewController.
Problem is that when I hit the "Cancel" button in AddViewController, the call back to the delegate seems not to execute. Symptomatically, the Cancel button behaves as if it were not even wired up. Programmatically, breakpoints seem to indicate that control leaves the cancelButton method, yet never reaches addViewControllerDidCancel.
I believe everything is wired properly, and here's the relevant code:
From AddViewController.h:
#import <UIKit/UIKit.h>
#import "WMDGCategory.h"
#import "WMDGActivity.h"
#class HomeViewController;
#protocol AddViewControllerDelegate <NSObject>
-(void) addViewControllerDidSave;
-(void) addViewControllerDidCancel:(WMDGActivity *) activityToDelete;
#end
#interface AddViewController : UIViewController <UIPickerViewDataSource,UIPickerViewDelegate>
#property (nonatomic, weak) id <AddViewControllerDelegate> delegate;
From HomeViewController.h:
#interface HomeViewController : UITableViewController <UITableViewDataSource,UITableViewDelegate,AddViewControllerDelegate>
From HomeViewController.m:
-(void) addViewControllerDidSave
{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
[localContext MR_saveToPersistentStoreAndWait];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
[self refreshData];
}
-(void) addViewControllerDidCancel:(WMDGActivity *) activityToDelete
{
[activityToDelete MR_deleteEntity];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
[self refreshData];
}
And from AddViewController.m:
- (IBAction)cancelButton:(UIBarButtonItem *)sender
{
[self.delegate addViewControllerDidCancel:self.thisActivity];
}
Can someone spot my mistake?
Thanks!
Edit in response to answer 1 below:
Actually, I did this in my prepareFroSegue method in HomeViewController:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
if ([[segue identifier] isEqualToString:#"addModal"])
{
UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
AddViewController *avc = (AddViewController *)navController.topViewController;
avc.delegate = self;
WMDGActivity *addedActivity = (WMDGActivity *)[WMDGActivity MR_createInContext:localContext];
avc.thisActivity = addedActivity;
}
}
Your missing assigning the delegate object of your AddViewController instance, from what I can see it's still nil
myAddViewController.delegate = self; // 'self' is an example, should be myHomeViewController instance
EDIT:
You are calling this in your code:
[self.delegate addViewControllerDidCancel:self.thisActivity];
Ask yourself what is the value of delegate? You have declared it, yes, but you have not set its value, so it must be nil. So when you instantiate your AddViewController you must set the delegate using the line I wrote before.
Related
I have implemented protocol in my app,
I have declared protocol method in loginViewController and i am calling protocolMethod from confirmViewController
Here is my code snippet:
loginViewController.h
#protocol FirstControllerDelegate<NSObject>
#required
-(void)protocolMethod;
#end
#interface loginViewController : UIViewController<FirstControllerDelegate>
#end
loginViewController.m
- (IBAction)loginBtnClicked:(id)sender {
confirmViewController *obj = [confirmViewController new];
obj.delegate=self;
[self performSegueWithIdentifier:#"confirmloginsegue" sender:self];
}
-(void)protocolMethod{
NSLog(#"--- This is not callled ---");
[self dismissViewControllerAnimated:NO completion:nil];
}
Code for
confirmViewController.h
#interface confirmViewController : UIViewController
#property (nonatomic, assign) id <FirstControllerDelegate> delegate;
#end
confirmViewController.m
(IBAction)continueClicked:(id)sender {
[_delegate protocolMethod]; //Calling protocol method...
[self dismissViewControllerAnimated:YES completion:nil];
}
Where i am doing mistake?
Please help and thanks in advance!
Wrong Approach and delegate initialisation
In this lines:-
confirmViewController *obj = [confirmViewController new];
obj.delegate=self;
[self performSegueWithIdentifier:#"confirmloginsegue" sender:self];
--- You are loosing the object here
Solution
In your attribute inspector give the identifier for that confirmViewController, now push the vc and set the delegate for the object confirmViewController like this:-
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ConfirmViewController *confirmViewController = [storyboard instantiateViewControllerWithIdentifier:#"ConfirmViewController"];
confirmViewController.delegate=self;
[self.navigationController pushViewController:confirmViewController animated:YES];
The delegating class does not exist for long enough to matter:
- (IBAction)loginBtnClicked:(id)sender {
confirmViewController *obj = [confirmViewController new];
obj.delegate=self;
[self performSegueWithIdentifier:#"confirmloginsegue" sender:self];
// obj is destroyed here
}
You need to implement prepareForSegue and set delegate there e.g.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
confirmViewController *destViewController = (confirmViewController *)segue.destinationViewController;
destViewController.delegate=self;
}
for further detail check this link:- http://www.appcoda.com/storyboards-ios-tutorial-pass-data-between-view-controller-with-segue/
You have to set delegate there. please,make some correction in your code like:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
confirmViewController *destViewController = (confirmViewController *)segue.destinationViewController;
destViewController.delegate=self;
}
You shoul use preparefrosegue to set your delegate object. so your code should be like this,
- (IBAction)loginBtnClicked:(id)sender {
[self performSegueWithIdentifier:#"confirmloginsegue" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
confirmViewController *obj = [confirmViewController new];
//or
confirmViewController *obj = [segue destinationViewController];
obj.delegate=self;
}
hope this will help ;)
If u are using View controller
SettingsHubViewController *settingVC = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"yourStoryBoardIdentifier"];
settingVC.delegate = self;
If u are using XIB load the NIB after that set the settingVC.delegate = self;
its working for me (I created OverLay view and loaded in main class)
I have gone through most of the previous related posts, but although I have followed them correctly (as far as i understood), I simply am not able to trigger the delegate method for below code.
Objective: ModalView generates a string *SQL_String. Press DONE to dismiss the ModalView and trigger the delegate method in the parentview to get that *SQL_String.
SearchModalViewController.h
#protocol SearchControllerDelegate
- (void)didDismissModalView:(NSString *)SQL_String;
#end
#interface SearchModalViewController : UIViewController
#property (nonatomic, assign) id <SearchControllerDelegate> searchDelegate;
- (IBAction)handleDone:(id)sender;
SearchModalViewController.m
#interface SearchModalViewController ()
#end
#implementation SearchModalViewController
#synthesize searchDelegate;
- (IBAction)handleDone:(id)sender {
[self dismissView:sender];
}
- (void)dismissView:(id)sender {
[searchDelegate didDismissModalView:#"Test"];
[self dismissViewControllerAnimated:YES completion:nil];
}
DetailViewController.m (My parent View Controller)
#interface DetailViewController () <SearchControllerDelegate>
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
SearchModalViewController *searchModal = [[SearchModalViewController alloc] init];
searchModal.searchDelegate = self;
}
PROBLEM:
Below delegate method is not getting triggered.
- (void)didDismissModalView:(NSString *)SQL_String {
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"The string = %#", SQL_String);
}
Any idea where I am doing wrong?
EDIT: Thank you guys. With your fast suggestions, I am able to close it down by adding below code instead of my previous IB connection.
- (IBAction)showSearchModal:(id)sender {
SearchModalViewController *searchModal = [self.storyboard instantiateViewControllerWithIdentifier:#"search"];
searchModal.searchDelegate = self;
searchModal.modalPresentationStyle = UIModalPresentationFormSheet;
searchModal.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:searchModal animated:YES completion:nil];
}
Here it goes
Change DetailViewController.m
- (IBAction)pushToSearch:(id)sender{
SearchModalViewController *searchModal = [self.storyboard instantiateViewControllerWithIdentifier:#"search"];
searchModal.searchDelegate = self;
[self presentViewController:searchModal animated:YES completion:nil];
}
And it will work.
Firstly, make sure your dismissView: of the SearchModalViewController is getting triggered.
Secondly, make sure your searchDelegate in the dismissView: method is not nil.
You need to set the delegate when you present the SearchModalViewController. The reason why your code doesn't currently work, is because the modal view controller's delegate is nil.
Update:
You set the delegate in prepareForSegue:sender:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)__unused sender
{
if ([[segue identifier] isEqualToString:#"modalSearch"])
{
SearchModalViewController *controller = (SearchModalViewController *)[segue destinationViewController];
controller.delegate = self;
}
}
I'm fairly new to Objective-C, programming. The one thing, I'm currently putting up with is passing a value from the first ViewController to the next one.
I've read this entry here and it didn't help me. Which is funny, since his answer has nearly 500 votes, so I must be the problem. What I did was the following.
I opened my PreviewViewController.m and added the following line #import "MainViewController.h" since I wanted to pass a value from the PreviewViewController to the `MainViewController. Then, when I switch the layouts ( which successfully works ) I want to pass a value.
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
As you can see, I want to pass the userId. For that, I also created a property in the MainViewController.h
#property ( nonatomic, strong ) NSString *userId;
Now, In my MainViewController.m I want to access the userId. But when I log it, the console tells me it is null. However, when I set the variable right before the NSLog it works, so it seems like the passing is the problem.
Additionally in the MainViewController.m I have the following line
#synthesize userId = _userId;
but even when I removed that line and changed the NSLog to NSLog(#"%#",self.userId); the same problem occurred.
How can I successfully pass the variables? Which step am I doing wrong?
EDIT
This is how I switch the layouts
UIViewController *viewController = [[MainViewController alloc]init];
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
[self presentViewController:viewController animated:YES completion:NULL];
Why not create a custom initializer and pass it in that way? Something like
- (id)initWithUserId:(NSString *)aUserId {
self = [super initWithNibName:#"MainViewController" bundle:nil];
if (self) {
self.userId = aUserId
}
return self;
}
Then you can just do:
MainViewController *mvc = [[MainViewController alloc] initWithUserId:#"1234"]
[self presentViewController:mvc animated:YES completion:nil];
If you are using storyboard then you should use prepareForSegue: method to pass data between view controllers. First Create the segue from your PreviewViewController to MainViewController, just control drag from your view controller to next viewcontroller to create segue. Use UINavigationController if you are using push segue. Use this method to segue and pass data
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"yourSegueIndentifier"]){
MainViewController *mvc = (MainViewController *) segue.destinationViewController;
mvc.userId = #"539897197";;
}
}
UIViewController *viewController = [[MainViewController alloc]init];
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
[self presentViewController:mainViewController animated:YES completion:NULL];
use mainViewController rather than viewController when presenting the view controller
Here we have two ways for passing data.
First is Custom Delegate
Second is NSNotification
Here we have two view controllers.
ViewController
and
SecondViewController
in SecondViewController
.h
#import <UIKit/UIKit.h>
#class SecondViewController;
#protocol SecondViewControllerDelegate <NSObject>
- (void)secondViewController:(SecondViewController *)secondViewController didEnterText:(NSString *)text;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, assign)id<SecondViewControllerDelegate> delegate;
#property (nonatomic, strong) IBOutlet UITextField *nameTextField;//It must connect as outlet connection
- (IBAction)doneButtonTapped:(id)sender;
#end
.m
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
//Either use NSNotification or Delegate
- (IBAction)doneButtonTapped:(id)sender;
{
//Use Notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"passingDataFromSecondViewToFirstView" object:self.nameTextField.text];
//OR Custom Delegate
[self.delegate secondViewController:self didEnterText:self.nameTextField.text];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
in ViewController
.h
#import <UIKit/UIKit.h>
#import "SecondViewController.h"
#interface ViewController : UIViewController<SecondViewControllerDelegate>
#property (nonatomic, strong) IBOutlet UILabel *labelName; //You must connect the label with outlet connection
- (IBAction)gotoNextView:(id)sender;
#end
.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//addObserver here...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFromPreviousViewControllerNotificationReceived:) name:#"passingDataFromSecondViewToFirstView" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
//addObserver Method here....
- (void)textFromPreviousViewControllerNotificationReceived:(NSNotification *)notification
{
// set text to label...
NSString *string = [notification object];
self.labelName.text = string;
}
- (IBAction)gotoNextView:(id)sender;
{
//If you use storyboard
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
//OR If you use XIB
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.delegate = self;
[self.navigationController pushViewController:secondViewController animated:YES];
}
//Calling custom delegate method
- (void)secondViewController:(SecondViewController *)secondViewController didEnterText:(NSString *)text
{
self.labelName.text = text; //Getting the data and assign the data to label here.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
For your understanding the code I create a simple passing data from one second view controller to first view controller.
First we navigate the view from first view controller to second view controller.
After that we send the data from second view controller to first view controller.
NOTE : You can either use NSNotification or Custom Delegate method for sending data from One View Controller to Other View Controller
If you use NSNotification, you need to set the postNotificationName for getting data in button action method.
Next you need to write addObserver in (sending data to your required View Controller) ViewController and call the addObserver method in same View Controller.
If you use custom delegate,
Usually we go with Custom Protocol Delegate and also we need to Assign the delegate here.
Very importantly we have to set the Custom Delegate Method in the Second View Controller.Because where we send the data to first view controller once we click the done button in second view controller.
Finally we must call the Custom Delegate Method in First View Controller, where we get the data and assign that data to label.Now you can see the passed data using custom delegate.
Likewise you can send the data to other view controller using Custom Delegate Methods
I tried and got the solution for passing the value between viewcontroller.
MainViewController *mainVC = [[self storyboard] instantiateViewControllerWithIdentifier:#"MainViewController"];
mainVC.userId = #"539897197";
[self presentViewController:mainVC animated:YES completion:Nil];
or using this way . You don`t use the storyboard use the following its working fine
MainViewController *mainVC = [[MainViewController alloc] init];
mainVC.userId = #"539897197";
[self presentViewController:mainVC animated:YES completion:Nil];
I'm trying to learn more about Objective C blocks and how they work. I've set up a simple project with two UIViewControllers embedded in a UINavigationController in Storyboard. I'm attempting to change the background color of the first ViewController's view from the second view controller. Here's some code:
ViewController.m
#implementation ViewController{
ColorBlock _colorBlock;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"theSegue"]){
SecondViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
vc.colorBlock = _colorBlock;
}
}
- (IBAction)moveToSecondViewController:(id)sender {
__weak id weakSelf = self;
_colorBlock = ^{
[[weakSelf view] setBackgroundColor:[UIColor redColor]];
};
}
SecondViewController.h
typedef void (^ColorBlock)(void);
#interface SecondViewController : UIViewController
#property (readwrite, copy) ColorBlock colorBlock;
#end
SecondViewController.m
- (IBAction)buttonTapped:(id)sender {
if(self.colorBlock){
self.colorBlock();
}
}
The first ViewController's background color isn't being changed because in the buttonTapped: method of SecondViewController.m, self.colorBlock is nil, causing the block invocation not to be called. I thought I had successfully set the block in prepareForSegue:sender:. Why is my block property nil?
In your prepareForSegue, the destination has already been instantiated. So assuming that SecondViewController is the destination, you can do:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"theSegue"]){
SecondViewController *vc = segue.destinationViewController;
NSAssert([vc isKindOfClass:[SecondViewController class]], #"destination is not SecondViewController class");
vc.colorBlock = _colorBlock;
}
}
I have a HomeViewController which has a tableView populated with the array tableViewArray (originally empty). When I tap on a barButton, I segue modally to another View Controller called OutsideViewController which has another tableView populated by a different array.
What I would like to do is the following:
When I tap on a row in my OutsideViewController, I would like to add the selected string value to the tableViewArray so that when I go back to HomeViewController, the tableView has that new item listed in the tableView.
So far, this is what I have tried:
In the -didSelectRowAtIndexPath method of my OutsideViewController.m I have this piece of code:
NSString *selectedRow = [outsideArray objectAtIndex:indexPath.row];
NSMutableArray *temporaryArray = [NSMutableArray arrayWithObject:selectedRow];
HomeViewController *homeVC = [[HomeViewController alloc] init];
homeVC.tableViewArray = temporaryArray;
That code works but the tableView in HomeViewController is still empty when I return. Do I have to reload the tableView data?
Am I doing this right?
This is how I have set up my View Controllers in Storyboard:
HomeViewController -(modal segue)-> Navigation Controller --> OutsideViewController
Also, the return from OutsideViewController to HomeViewController is done by this line of code:
[self dismissViewControllerAnimated:YES completion:^{ }];
What you're doing wrong is you're allocationg a new HomeViewController. What I would do is keeep a reference to your HomeViewController in your OutsideViewController. Here is how.
First, in OutsideViewController.h, create a property, like this :
#property (nonatomic, weak) HomeViewController *homeVC;
Don't forget to add #class HomeViewController; in your .h, and #import "HomeViewController.h" in your .m
In HomeViewController, implement the prepareForSegue: method like this (replace ModalSegueIdentifier with your segue's identifier) :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ModalSegueIdentifier"]) {
OutsideViewController *modalVC = (OutsideViewController*)segue.destinationViewController;
modalVC.homeVC = self;
}
}
Then, in OutsideViewController.m, instead of doing :
HomeViewController *homeVC = [[HomeViewController alloc] init];
homeVC.tableViewArray = temporaryArray;
Do this :
_homeVC.tableViewArray = temporaryArray;
When you leave your modal VC, your HomeVC will have the correct array. Don't forget to refresh your UITableView !
NB: Of course, there are many other ways, and it's maybe not the best one. But still, it should work.
You can achieve this too using delegation. You have to create a protocol in your OutsideViewController with a method that is responsible for sending the new object to your HomeViewController. Do this in OutsideViewController.h:
#protocol OutsideViewDelegate <NSObject>
- (void)OutsideViewController:(OutsideViewController *)controller didAddObject:(NSString *)object;
#end
In the implementation file you have to change a little bit the didSelectRowAtIndexPath: method:
NSString *selectedRow = [outsideArray objectAtIndex:indexPath.row];
[self.delegate OutsideViewController:self didAddObject:selectedRow];
In your HomeViewController.h you have to make your class conforms to the protocol:
#interface HomeViewController : UIViewController <OutsideViewDelegate>
After, create a property for the delegate:
#property (nonatomic, weak) id <OutsideViewDelegate> delegate;
To finish the process, implement the protocol in your HomeViewController.m to receive the new object from the OutsideViewController:
- (void)OutsideViewController:(OutsideViewController *)controller didAddObject:(NSString *)object
{
if (object != nil)
{
[self.tableViewArray addObject:object];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The code above depends of if your tableViewArray object is mutable or not. If it's not, you can change the type of the object argument in the protocol method to an inmutable array object and just assign tableViewArray to the new array.
EDIT:
In the prepareForSegue: method don't forget to set the delegate:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"SEGUE_IDENTIFIER"]) {
OutsideViewController *outsideVC = (OutsideViewController *)[segue destinationViewController];
[outsideVC setDelegate:self];
}
}
First of all make sure you alloc, init your tableViewArray in HomeViewController
Second , In this line
HomeViewController *homeVC = [[HomeViewController alloc] init]
you are creating a new reference to your HomeViewController which is not correct, you need to pass correct reference, possibly creating HomeViewController variable in your OutsideViewController
Even though you correctly do first and second suggestion you will still see an empty tableview because you dont reload the tableview, somehow you need to fire [self.tableview reloadData]; method.
That means; you need to learn Delegate or NSNotifications pattern to communicate between child->parent scenarios
How do I set up a simple delegate to communicate between two view controllers?
http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_nsnotificationcenter/
For your question just create a delegate in your Outside;
in your OutsideViewController.h
#import <UIKit/UIKit.h>
#protocol OutsideDelegate;
#interface{}//bunch of interface stuff
// Declare a property for the delegate
#property (weak) id <OutsideDelegate> delegate;
#end
// Protocol Header
#protocol OutsideDelegate <NSObject>
#optional
- (void)dismissPop:(NSMutableArray *)list;
#end
in your OutsideViewController.m
#synthesize delegate;
//run delegate method
[delegate dismissPop:temporaryArray];
[self dismissViewControllerAnimated:YES completion:^{ }];
in your HomeViewController.h
#import "OutsideViewController.h"
#interface OutsideViewController : UITableViewController<OutsideDelegate>
{}
#property (strong, nonatomic) OutsideViewController *pvc;
in your HomeViewController.m
#synthesize pvc;
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"your segue"]) {
pvc = [segue destinationViewController];
[pvc setDelegate:self];
}
}
// delegate callback function
- (void)dismissPop:(NSMutableArray *)list {
self.tableViewArray=list;
[self.tableView reloadData];
}
Another Solution Would be
Change your view stack to this:
Navigation Controller --> HomeViewController -(push segue)--> OutsideViewController
and apply rdurand's answer
and add this to your HomeViewController :
-(void)viewDidAppear:(BOOL)animated
{
[self.tableview reloadData];
}
In this solution since you are just push-pop viewcontrollers in a nabigation stack viewDidAppear will be called in HomeViewController everytime when you pop OutsideViewController.