How to delegate back to the previous veiwcontroller - iphone

here is my navigation on my app
1) homescreenview controller-->composemessageviewcontroller (i am able to use delegate to send data back to homescreenview)
2)homescreenview controller -->messageslistcontroller(tableview)-->detailmessageviewcontroller(which is where my reply button is).
my problem is when i hit reply i want to send back information to homescreenviewcontroller with delegate . how can i do this?
Thanks in advance.
----UPDATE
#XJones, thanks for the detailed explanaion. is this what is should be doing in when i push detailview? please correct me if i am wrong.
(void)pushDetailMessageController{
DetailMessagetController *detailmessage = [[DetailMessagetController alloc] init];
detailmessage.delegate = self;
// push messageListController onto navigation controller here
[detail release];
}

that's a very general question. you're basically asking how to pass information from one controller to another controller. There are different ways to do that, a protocol (what a delegate generally communicates through) is one of them. The quickest thing you can do w/o making assumptions in your code that may be problematic later would be to pass the homeScreenController along as you push messageListController and then detailMessageController. You'll need to define an iVar and property in messageListController and detailMessageController to do this.
Something like:
in messageListController.h:
#import "HomeScreenController.h"
#interface messageListController : UITableViewController {
// your iVars
HomeScreenController *homeScreenController;
#end
#property (nonatomic, assign) HomeScreenController * homeScreenController;
add the same iVar and property for homeScreenController to detailMessageController.
in homeScreenController.h:
- (void)pushMessageListController
{
MessageListController *messageListController = [[MessageListController alloc] init];
messageListController.homeScreenController = self;
// push messageListController onto navigation controller here
[messageListController release];
}
in messageListController do the same thing as above when creating and pushing detailMessageController. Now, in detailMessageController you can send messages directly to homeScreenController.
If you want to generalize the above implementation so your controllers aren't specifically knowledgeable about each other then you can define a protocol and pass homeScreenController through as a delegate supporting that protocol.

How about adding a method in the messageslistcontroller? I personally would add the delegate "homescreenview" to the detailmessageviewcontroller as the messageslistcontroller doesn't have anything to do with the reply and apperantly the homescreenviewcontroller does.
when you create the detailmessageviewcontroller in the messageslistcontroller do this:
detailmessageviewcontroller.homeScreenDelegate = self.delegate;

One approach (without delegation)
as you are using navigationController, so
[[self.navigationController viewControllers]objectAtIndex:0] will always return you homeScreenViewController.....you can use this object....
Thanks,

Related

ViewDidLoad executes slowly while pushing viewcontroller

I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.

Xcode - update ViewController label text from different view

I have two view Controllers in my project ViewController, SettingsView. Here I am trying to update the ViewController's label, when i click on the SettingsView's back button. NSLog is working fine, but the label is not updating...
Please help me....
SettingsView.m
-(IBAction)backToMain:(id) sender {
//calling update function from ViewController
ViewController * vc = [[ViewController alloc]init];
[vc updateLabel];
[vc release];
//close the SettingsView
[self dismissModalViewControllerAnimated:YES];
}
ViewController.m
- (void)updateLabel
{
NSLog(#"Iam inside updateLabel");
self.myLabel.text = #"test";
}
Could you please tell me whats wrong with my code? Thank you!
You have to implement protocols for that. Follow this:
1) In SettingView.h define protocol like this
#protocol ViewControllerDelegate
-(void) updateLabel;
#end
2) Define property in .h class and synthesis in .m class..
#property (nonatomic, retain) id <ViewControllerDelegate> viewControllerDelegate;
3) In SettingsView.m IBAction
-(IBAction)backToMain:(id) sender
{
[viewControllerDelegate updateLabel];
}
4) In ViewController.h adopt protocol like this
#interface ViewController<ViewControllerDelegate>
5) In viewController.m include this line in viewDidLoad
settingView.viewControllerDelegate=self
Your label is not updating because , you are trying to call updateLabel method with a new instance.
You should call updateLabel of the original instance of viewcontroller from which you have presented your modal view.
you can use a delegate mechansim or NSNotification to do the same.
Delegate mechnaism would be clean. NSNotification is quick and dirty.
You are not exactly calling the correct vc. This is because you are creating a new instance of that class and calling the updateLabel of that instance.
You have a few options.
Either implement it as a delegate callBack (delegate messagePassing, or delegate notification - however you want to call it) to notify that class instance to call the updateLabel method.
Use the original instance VC as a dependency injection into the class that you are on right now, and use that instance to call the updateLabel
Use NSNotifications / NSUserDefaults to communicate between viewControllers and setup a notification system for your actions. This is quite easy, but not really great in the long run.
I would RECOMMEND option 1 (or) option 2.
Simply declare like this in SettingsView class:
UILabel *lblInSettings;// and synthesize it
Now assign like below when you presenting Settings viewController:
settingsVC.lblInSettings=self.myLabel;
Then whatever you update in lblInSettings it will be present in MainView obviously....
no need for any delegate methods or updating methods.
Means if you assign at the time of dismissing like
lblInSettings.text=#"My new value";
then self.myLabel also will be updated.
Let me know if you have any queries?

returning an object back to root viewcontroller

I am aware that this topic has been covered already in many places, but while going through the different answers, I could not find a solution that fits my case.
Basically what I am trying to do is very simple. I have a view controller with a table view, and a + button that fires a second view controller where the user can enter, say a name, and this name is then added to the first view controller table view. Think about Contacts on the iPhone where you can add a new person (or Amazon where you can add a new credit card).
My question is - what is the best way to return this string (in this case) back to the view controller where the table is?
People suggested using NSDefaults, Delegate, or singleton none of which are really good for this case (each one for its own reason). I really just need to return a simple string.
Thank you for your help.
If you are navigating from View Controller A ---> View Controller B, which is your case here, and then you want to pass information from B -> A, it is recommended to use a loose coupling, like delegation. There are a couple of things as you discussed, like NSUserDefaults, singleton, NSNotification, and may be many more.
But delegation is the better and standard approach to do it.
Here's what you need to do (please note I am typing this off the top of my head so the compiler may have some issues with my syntax).
In your child view controller interface:
#protocol ChildDelegate <NSObject>
- (void)didConfirmName:(NSString*)name
#end
#interface ChildViewController : UIViewController
...
#property (nonatomic, assign) id<ChildDelegate> delegate;
And implementation, inside the method called when the user confirms whatever they need to confirm:
- (void)myCustoMethod
{
...
if ([self.delegate respondsToSelector:#selector(didConfirmName:)])
[self.delegate didConfirmName:NAME_STRING];
}
And in your parent view controller, when you are instantiating the child view controller, assign SELF as the delegate:
ChildViewController *vc = [[ChildViewController alloc] init...];
vc.delegate = self;
And implement:
- (void)didConfirmName:(NSString*)name
{
// Do whatever you want with the name here...
}
Also make sure you tell the compiler that you are implementing the protocol :
#interface ParentViewController () <ChildDelegate>
#end
You don't return the new thing to the TableView. What you need is to update the source of information that feeds your tableView.
So where the data live is the place you need to go and add it, when you will come back to the UITableViewController you may need to tell the UITableView to reload it's data.
There is a handle on each UIViewController to it's parent if you absolutely need to communicate with it.
UIViewController *parentVC = aViewController.parentViewController;
Assuming that the ViewController that needs to get updated is the root view controller of the app, you can do the following:
YourViewController * yvc = [(YourAppDelegate *)[[UIApplication sharedApplication] delegate] viewController];
[yvc updateString:#"Updated String"];
Remember to:
#import "YourAppDelegate.h"
But Honestly I would use the delegate pattern or a NSNotification.
Three good options:
1) Have the first view controller pass itself to the second view controller, so that the second controller can send messages to the first. Ideally, create a protocol that the first controller adopts, so that the second controller doesn't depend on the type of the first controller but only cares that it implements the protocol:
#protocol Nameable
#property(copy) NSString* name;
#end;
#interface FirstViewController <Nameable>
//...
#end
#implementation FirstViewController
#synthesize name;
//...
#end
Then the first controller can do this:
SecondViewController *second = [[SecondViewController alloc]
initWithNibName:nil]; second.thingToName = self;
[self.navigationController pushViewController:second animated:YES];
And when the time comes, the second controller can do this:
self.thingToName.name = nameString;
The details aren't really that important -- the main thing to know here is that if you want to send a message to an object, you first need a pointer to the object. When the first view controller sets second.thingToName = self, it's providing that pointer.
2) Have the first view controller create a data object in which the second view controller can store the data, like this:
#interface Person <Nameable>
//...
#end
#implementation Person
#synthesize name;
//...
#end
Now the first view controller can create a new Person and pass that:
SecondViewController *second = [[SecondViewController alloc]
Person *person = [[Person alloc] init];
[self.people addObject:person];
initWithNibName:nil]; second.thingToName = person;
[person release];
[self.navigationController pushViewController:second animated:YES];
This is similar to the first approach, only the thing that's receiving the name isn't the view controller here, it's some sort of data container (Person).
You can see the value of the protocol here, too -- notice that the SecondViewController class doesn't change at all between the first and second approaches. It doesn't care whether it's talking to a view controller or an instance of Person or anything else... as long as the thing implements the Nameable protocol, it's happy.
3) Reverse the direction of communication. Instead of making the second view controller send the string, have its parent get the string. This may well be the simplest solution, although it does require that the parent has some way to know that the child is done. It'd go something like this:
#implementation FirstViewController
//...
- (IBAction)addNewName:(id)sender
{
self.secondController = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:self.secondController animated:YES];
}
- (void)viewWillAppear
{
if (self.secondController != nil) { // we must be returning from the child
self.name = self.secondController.name;
self.secondController = nil;
}
}
#end

Messaging between ViewControllers Using Delegates

I'm trying to send an array from one viewController to another using protocols and delegates. I declared the delegate on viewcontroller B and used the following code in viewcontroller A to send the message from A to B. The protocol's method is didReceiveMessage. Unfortunately, the message never arrives.
Attached is the code from viewController A
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:gvc animated:YES];
[gvc release];
[delegate didReceiveMessage:brain.internalExpression];
}
and the code from viewcontrollerB
- (IBAction) ViewdidLoad {
CalculatorViewController *cvc =[[CalculatorViewController alloc] init];
cvc.delegate=self;
[cvc release];
}
- (void) didReceiveMessage:(NSMutableArray *)expression {
NSLog(#"message received from CalculatorAppDelegate");
}
Any suggestions would be greatly appreciated.
I'm not sure what are you doing in second sample? You created an object, assign its delegate property and then released it. Why?
If that is code from your application it could probably be from releasing cvc at the end of your ViewDidLoad method.
init would give it a retain count of 1, and the release would take it back to 0.
Also the code seems sort of backwards, if you wanted to set view A as the delegate for view B, you would do so in view A.
Unless there is a more complex reason to use a delegate that I'm not seeing from the code, I would just keep a pointer around to the child if you really need to send it messages.
Like others have posted, you are just getting rid of the Calculator VC after creating it. I would recommend adding an #property to the second VC and using it to store a pointer to the Calculator. It should be a retain property. Then set the delegate of that property to self.
Also, you make sure to use an assign property for the delegate value, and try to use the property syntax (self.delegate) whenever possible.
There could absolutely be more going on here, so if this doesn't actually solve the problem try and post up more of your code (header files, what connections are made in IB, etc.)
Edit: For the record, the method is -(void)viewDidLoad, not -(void)ViewDidLoad, so that could be contributing to the problem.
As you said you are trying to pass an array from one view controller to another.Well i use in this way.Here is the code
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
gvc.myArray=infoArray;
[self presentModalViewController:gvc animated:YES];
[gvc release];
}
Where myArray is array in your GraphingViewController,You just need to make property of this array with retain attribute,simply as
#property(nonatomic,retain)NSMutableArray *myArray;
And you need to Synthesize it as well.

How do I pass an object into a delegate?

I am creating a delegate view controller and presenting it to the user to perform an action but I would like to change a NSString on the delegate view controller based on the originating view controller. For example if the delegate view controller is a delegate of viewControllerA, then display Foo, but if its a delegate of viewControllerB then display Blah. ALthough I cant figure out how to pass some sort of information that indicates what the originating view controller is. I noticed that if i do an NSLog(#"I'm from %#",[self delegate]); it will tell me what the originating view controller is, as well as the memory address, but I cant seem to translate that into an NSString object to examine its value. If theres a way to make that work, or a better way to do this then that works too...
- (IBAction)editDate {
DatePickerViewController *datePickerView = [[DatePickerViewController alloc] init];
datePickerView.delegate = self;
datePickerView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:datePickerView animated:YES];
[datePickerView release];
}
It seems like you're using some terminology in ways that are different from what most Objective-C coders would mean.
Here you're instantiating a view controller to show as a modal view. That view controller has a property called delegate that allows it to call some methods to report changes to its state. That doesn't make it a "delegate view controller", that makes it "an object with a delegate".
You happen to be using another view controller class as the delegate, but any object that implements the methods that DatePickerViewController objects want to call to report changes could be assigned to that delegate property.
I think that the question you're asking is "how do I make the DatePickerViewController display different information depending on what kind of object it's reporting to?", and the answer is much the same as "how do I make a UILabel show different text depending on the view controller that created it?"—you set properties or call methods on in when you create it.
If you really just want to pass a string to DatePickerViewController, you could add an NSString* property to DatePickerViewController and set it with arbitrary text, with
datePickerView.myString = #"some information that you want";
You could use the class of the delegate.
if([[self delegate] isKindOfClass:[ViewControllerA class]]) {
[self doViewControllerAThings];
}
else {
...
}