I tried many custom delegate examples in my code to send data from view B back to parent view A. Why am I getting NULL? There should be something I am missing. Please help. How can I call setupDate to receive (NSDate *)setAlarmDate in Parent view?
Parent view
#import "SetupViewController.h"
#interface ViewController : UIViewController <SetupViewControllerDelegate> {
NSTimer *timer;
}
#end
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"setupview"]) {
NSLog(#"prepare for segue");
SetupViewController *svc = [[SetupViewController alloc] init];
svc.delegate = self;
-(void)setupDate:(NSDate *)setAlarmDate{
NSLog(#"Hey Hey");
NSLog(#"%#", setAlarmDate);
}
#end
View B
#protocol SetupViewControllerDelegate <NSObject>
#required
-(void)setupDate:(NSDate *)setAlarmDate;
#end
#interface SetupViewController : UIViewController
#property (nonatomic, strong) id <SetupViewControllerDelegate> delegate;
//IB
#property (weak, nonatomic) IBOutlet UIDatePicker *pDatePicker;
#end
.m
#import "SetupViewController.h"
#import "ViewController.h"
#implementation SetupViewController
-(void)viewWillDisappear:(BOOL)animated{
NSDate *date = [self.pDatePicker date];
[[self delegate] setupDate:date];
}
#end
You should access the UIViewController using the segue's destinationViewController property instead of allocating an instance of your own, which is why your alarmDate = NULL.
E.g.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"setupview"])
{
NSLog(#"prepare for segue");
SetupViewController *svc = [segue destinationViewController];
svc.delegate = self;
}
}
Also, as #Neru mentioned your delegate should be qualified as weak and not strong. The reason for this is because you will cause a retain cycle if you do not explicitly set the delegate = nil in the dealloc method.
Related
I have my first viewController "SFCrearMomentoViewController" and the second view is a tableViewController "MasterViewController". I need to select a row didSelectRowAtIndexPath and call the method didCelected which is implemented in my "first viewController" and close the view using dismissViewControllerAnimated.
The problem is that the method didCelected is never called. I've already tested this code in a test project using "two viewControllers" and it works but I don't know what's the problem in my current project.
SFCrearMomentoViewController.h
...
#import "MasterViewController.h"
#interface SFCrearMomentoViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextViewDelegate, MasterViewControllerDelegate>{
UIImagePickerController *picker;
}
...
SFCrearMomentoViewController.m
...
-(void)didSelected:(NSString *)nombre{
NSLog(#"didSelected method %#", nombre);
}
#end
MasterViewController.h
#import <UIKit/UIKit.h>
#protocol MasterViewControllerDelegate <NSObject>
#required
-(void) didSelected:(NSString *)nombre;
#end
#interface MasterViewController : UITableViewController
#property (weak, nonatomic) id <MasterViewControllerDelegate> delegate;
#end
MasterViewController.m
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate didSelected:#"row selected"];
[self dismissViewControllerAnimated:YES completion:nil];
}
...
Solution: I had to set the delegate self. I implemented it in the Segue.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.destinationViewController isKindOfClass:[MasterViewController class]]) {
MasterViewController *nextView = segue.destinationViewController;
nextView.delegate = self;
}
}
I had the same problem a few hours ago. In my case I set the delegate in prepareForSegue to the segue.destinationViewController but my destination view controller wasn't the View Controller with the delegate, it was a UINavigationController.
So I used this one:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"modalNeuerRaum"])
{
[(MyCustomModalViewController*)[(UINavigationController*)segue.destinationViewController viewControllers][0] setDelegate:self];
}
}
That worked for me, but I don't know if you have a UINavigationController..
You need to set the delegate in SFCrearMomentoViewController, you can do it in viewDidLoad, for example.
masterViewController.delegate = self;
I assume that you allocated and initialised new masterViewController object of MasterViewController type.
I have a publicly declared NSString, NSString *characterString in my mainViewController.m and just before I perform a [self performSegueWithIdentifier:#"segueToFinal" sender:self]; I update this characterString with the latest data. like so:
characterString=[NSString stringWithFormat:#"final string %#",truncatedString];
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
When the new view controller finishes appearing, the user will press a button some time that will call a method that will want the characterString and possibly other publicly declared instance variables from that previous view controller. I was trying [[mainViewController alloc]getCharacterString] (with getCharacterString being a method implemented in mainViewController) but that of course creates a new instance of mainViewController and does not solve the issue.
How Can I access the data currently in 'characterString' and other variables from the old view controller?
Keep all you global variables in you AppDelegate:
YourAppDelegate.h
#interface BTAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSString * characterString;
#end
YourViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
YourAppDelegate* app = (YourAppDelegate*)[UIApplication sharedApplication].delegate;
app.characterString = #"Hello";
}
YourSecondViewController.m
- (IBAction)pressButton:(id)sender{
YourAppDelegate* app = (YourAppDelegate*)[UIApplication sharedApplication].delegate;
app.characterString = #"Hello2";
}
etc.
Try this out :
#interface NewVC :UIViewController
#property (nonatomic) NSString *newCharacterString;
#end
Now in the mainViewController import the NewViewController class :
#import "NewViewController.h"
.......
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NewViewController *destinationViewController = [segue destinationViewController];
destinationViewController.newCharacterString = self.characterString;
}
and you can access the value of the character string in your NewViewController through the newCharacterString property.
HTH :)
The only thing I can think of is passing the old data from the view controller along with the segue. Or, you could even pass the entire old view controller if you have multiple properties you want to keep.
In the new VC (the one being segued to), add a property that you want to keep from the old VC (the one being segued from) such as the NSString *.
#interface NewVC :UIViewController
#property (nonatomic) NSString *newCharacterString;
#end
Then, while you are segue - ing, just pass the value to the new VC.
characterString=[NSString stringWithFormat:#"final string %#",truncatedString];
newViewControllerIWantToPassAValueTo.newCharacterString = characterString;
// passes new string forward to View Controller
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
Why you dont pass this characterString to the secondViewController
In SecondViewController:
#interface SecondViewController:UIViewController
#property (strong, nonatomic) NSString *newCharacterString;
#end
In MainViewController:
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
And you have to add the preparePerform Segue to MainViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
SecondViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
vc.newCharacterString = self.characterString;
}
}
And you could use newCharacterString in SecondViewController without callback to the MainViewController
I have three navigation buttons in a view controller, two of which I process locally and one which launches an edit view controller. I have created a segue from the body of the first UITableView to the Navigation Controller of the destination view controller (the edit view controller).
The button works fine (i.e. launches the edit view) but it doesn't receive the string I send to it. Could someone please help. I have tried all of the suggestions given to other related questions but cannot seem to fix this. Following is the related code.
Source controller.h:
#import <UIKit/UIKit.h>
#import "EQHorseAddViewController.h"
#import "EQDatabase.h"
#interface EQHorseDetailViewController : UITableViewController
#property (strong, nonatomic) EQHorseAddViewController *addViewController;
Source controller.m:
#import "EQHorseDetailViewController.h"
#interface EQHorseDetailViewController ()
#end
#implementation EQHorseDetailViewController
#synthesize addViewController;
-(void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *deleteItem= [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
target:self
action:#selector(deleteHorse:)];
UIBarButtonItem *editItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self
action:#selector(editHorse:)];
NSArray *actionButtonItems = [[NSArray alloc] initWithObjects:deleteItem, editItem,
nil];
self.navigationItem.rightBarButtonItems = actionButtonItems;
}
-(void)editHorse:(id)sender
{
NSLog(#"EditingHorse");
self.addViewController= [[EQHorseAddViewController alloc] init];
addViewController.resultSegue = #"Edit Horse Details";
[self performSegueWithIdentifier:#"EditHorseDetails" sender: self];
}
Destination View Controller.h:
#import <UIKit/UIKit.h>
#import "EQHorseDetailsInfo.h"
#import "EQDatabase.h"
#class EQHorseAddViewController;
#protocol EQHorseAddViewControllerDelegate <NSObject>
- (void) eqHorseAddViewControllerDidCancel:(EQHorseAddViewController *)controller;
- (void)eqHorseAddViewControllerDidSave: (EQHorseAddViewController *)controller;
#end
#interface EQHorseAddViewController : UITableViewController
<EQHorseAddViewControllerDelegate>
#property (strong, nonatomic) EQHorseAddViewController *addViewController;
#property (nonatomic, weak) id <EQHorseAddViewControllerDelegate> delegate;
#property (strong,nonatomic) NSString *resultSegue;
Destination View Controller.m
#import "EQHorseAddViewController.h"
#import "EQDatabase.h"
#import "EQHorseDetailsInfo.h"
#interface EQHorseAddViewController ()
#end
#implementation EQHorseAddViewController
#synthesize resultSegue;
- (void)viewDidLoad
{
NSLog(#"resultSegue %#",resultSegue);
}
Thanking you in advance.
It looks like you're referring to the code in editHorse:, which allocates an EQHorseAddViewController and sets a property on it. The problem with that code is that the view controller it allocates bears no relation to the vc destination of the segue. That method simply creates a vc and then instantly discards it.
Remove the allocation and instead implement prepareForSegue:. The segue object passed to that method will have a property called destinationViewController. That's the view controller that's about to be pushed onto the navigation. That's the one on which you should set the resultSegue property.
-(void)editHorse:(id)sender
{
NSLog(#"EditingHorse");
[self performSegueWithIdentifier:#"EditHorseDetails" sender: self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"EditHorseDetails"]) {
EQHorseAddViewController *vc = segue.destinationViewController;
vc.resultSegue = #"Edit Horse Details";
}
}
You need to define the new controller as follows:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
EQHorseAddViewController *ivc = [storyboard instantiateViewControllerWithIdentifier:#"EditHorseDetails"];
ivc.resultSegue = #"Edit Horse Details";
Perhaps you'll need this method too
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"EditHorseDetails"]) {
// Get destination view
EQHorseAddViewController *vc = [segue destinationViewController];
vc.resultSegue = #"Edit Horse Details";
}
}
-(void)editHorse:(id)sender
{
NSLog(#"EditingHorse");
resultSegue = #"Edit Horse Details";
[self performSegueWithIdentifier:#"EditHorseDetails" sender: self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UINavigationController *navigationController = segue.destinationViewController;
EQHorseAddViewController *addViewController = [navigationController viewControllers]
[0];
// Get destination view
NSLog(#"ResultSegue in Prepare - %#",resultSegue);
addViewController.resultSegue = #"Edit Horse Details";
}
I have a ViewController1 with a UILabel that can present a ViewController2 with a Modal Segue. I have a UITextField in ViewController2 that I need to access from ViewController1 so I can set my UILabel with the collected text.
I've tried working with the prepareForSegue without success. What should I do?
EDIT:
I'm using a Delegate, but I'm doing something wrong. Here's the code I'm using in ViewController2.h:
#class ViewController2;
#protocol VCProtocol
-(void)setName:(NSString *)name;
#end
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id<VCProtocol> delegate;
#property (strong, nonatomic) IBOutlet UITextField *nameField;
- (IBAction)setButton:(id)sender
#end
ViewController2.m
-(IBAction)setButton:(id)sender
{
[self.delegate setName:nameField.text];
}
I conform to VCProtocol in my ViewController1.h. Then, in my ViewController1.m, I have this code:
- (void)setName:(NSString *)name
{
self.firstSignatureNameLabel.text = name;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqual:#"Sign"])
{
ViewController2 *VC = [segue destinationViewController];
VC.delegate = self;
}
}
You can create a protocol and set VC1 as delegate of VC2, and using prepareForSegue to set VC1 as VC2's delegate should work. I know that you said it didn't work, but I can't see why. Have a try with this :
Give an identifier to your segue (on storyboard), and implement prepareForSegue as shown below :
VC2Delegate.h :
#protocol VC2Delegate
- (void)updateLabel:(NSString *)text;
#end
VC1.h :
#import "VC2Delegate.h"
#interface VC1 : UIViewController <VC2Delegate>
// All your stuff
#end
VC1.m :
- (void)updateLabel:(NSString *)text {
[_label setText:text];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"YourSegueIdentifier"]) {
VC2 * vc = [segue destinationViewController];
[vc2 setDelegate:self];
}
}
VC2.h :
#import "VC2Delegate.h"
#interface VC2 : UIViewController
#property (weak, nonatomic) id<VC2Delegate>delegate;
#end
VC2.m
- (void)textWasUpdated { // or whatever method where you detect the text has been changed
if (_delegate)
[_delegate updateLabel:[_textView text]];
}
Tell me if it works. Else, is prepareForSegue even been called ?
EDIT : Updated my answer (wasn't exactly what you needed).
But as you say it doesn't work :
Is prepareForSegue called ?
If so, is the delegate method called ?
If the delegate method isn't called, check that delegate is not nil.
You might want to remove the segue, and present it modally by yourself, using presentViewController:animated:completion:, like that :
- (IBAction)buttonWasTapped {
static NSString * const idModalView = #"modalView";
static NSString * const storyBoardName = #"MainStoryBoard"
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyBoardName bundle:nil];
VC2 * vc = [storyboard instantiateViewControllerWithIdentifier:idModalView];
[vc setDelegate:self];
[self.navigationController presentViewController:vc2 animated:YES completion:nil];
}
When I ran into this problem, I chose the singleton approach and it works.
I have two view controllers. MainViewController pushes SignUp ViewController. Once SignUp View Controller dismisses, I need access to the same instance of MainViewController to update a UIButton.title.
.h MainView
//MainViewController.h
#import <UIKit/UIKit.h>
#interface MainViewController : UITableViewController
-(void)loggedIn;
#end
.m
#interface MainViewController ()
#end
-(void)loggedIn
{
NSLog (#"This is Logged in inside MainView.m");
self.logInOutButton.title = #"Logout";
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
Signup *signUp = [destinationViewController isKindOfClass:[SignUp class]] ? (Signup*)destinationViewController : nil;
signUp.mainViewController = self;
NSLog(#"Preapre For Segue %#", self);
}
.h SignUp
#import <UIKit/UIKit.h>
#import "MainViewController.h"
#interface SignUp : UIViewController
#property (strong, nonatomic) MainViewController *mainViewController;
#end
.m
#synthesize mainViewController;
- (void) loggedIn
{
NSLog(#" MainViewController %#", mainViewController);
[mainViewController loggedIn];
self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
When I do NSLog I'm getting different values:
Preapre For Seg <STMainViewController: 0x9f7d600>
MainViewController (null)
Create a protocol / delegate. There are numerous resources on how to achieve this. In summary, you create the protocol on the destination viewController with the method you wish to run on the source and then you subscribe to that delegate from the source viewController.
Protocol Delegate
Dont forget to set the delegate from your prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
Signup *signUp = segue.destinationViewController;
signup.delegate = self;
NSLog(#"Preapre For Segue %#", self);
}