I have some weird issue here as somehow I am unable to pass NSString from one class to another. I deployed the same method that worked on other classes.
I am trying to pass a string from secondViewController to my firstViewController. Here's what I did.
firstViewController.h
NSString *pickUpAddressString;
#property (nonatomic, retain) NSString *pickUpAddressString;
firstViewController.m
#synthesize pickUpAddressString;
-(void) viewWillAppear:(BOOL)animated {
NSLog(#"pickUpAddressString is %#", pickUpAddressString); // why it's null here?
PickUpAddress.text = pickUpAddressString; // PickUpAddress is a UITextField
}
secondViewController.m
FirstViewController *controller = [[FirstViewController alloc]init];
controller.pickUpAddressString = selectedAddress; // here, I set the string
NSLog(#"selected address :%#\npickUpAddressString:%#", selectedAddress, controller.pickUpAddressString); // I had verified that both strings are valid.
[self.navigationController popViewControllerAnimated:YES]; // pop to firstView
You are creating a new instance of FirstViewController...
FirstViewController *controller = [[FirstViewController alloc]init];
...different from the original instance that (I'm assuming) pushed the SecondViewController and to which you are returning via popViewControllerAnimated:.
Basically, what you need is to pass data back to the controller that pushed the SecondViewController, in this case, the FirstViewController.
Perhaps, the easiest way to achieve this is what #Ladislav suggested in his comment:
NSArray *viewControllers = [self.navigationController viewControllers];
FirstViewController *firstVC = [viewControllers objectAtIndex:[viewControllers count] - 2];
However, keep in mind that this introduces a direct dependency between SecondViewController and FirstViewController. Or, in other words, SecondViewController is now tightly coupled to FirstViewController.
In a nutshell, when it comes to pass data back up the hierarchy, it is a best practice to use loose coupling to avoid direct dependencies between your view controllers (tight coupling). The benefits of doing so include code reusability and testability. In order to achieve loose coupling you need to define a generic interface for observers (e.g. delegation, notifications, etc).
It is also worth mentioning the importance of putting state information in model objects. Avoid putting data inside the controllers, unless it's strictly presentation data.
More on this topic: What's the best way to communicate between view controllers?
Related
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
I am trying to pass a string back and forth between the view Controllers, so for example as soon as I click on a tab bar button (+) in the first View, second view opens (PresentModalViewController) and it has a Text Field. So anything I type, I take it into a string(this string is an object of the first view) and I am trying to append that string to a tableview loaded in the first View.
Note: My string object is declared like this
View1.h
NSString *string
#property (copy) NSString *string;
View1.m
#synthesize string;
And in the View 2 I am passing the textField Value like this
View2.m
View1 *view1 = [[View1 alloc] initWithNibName:#"View1" bundle:nil];
view1.string = [[NSString alloc] initWithFormat:#"%#", TextField.text];
Problem - When I NSLog this value inside the View2, it grabs the value from the Text Field but in order for me to load the previous view, I need to dismiss this View2. So as soon as this View2 is dismissed when I try to access the same string object in my view 1. It says the string object is null.
Question - Could someone tell me
1. How to get the text Field value from view 2 to view 1 after dismissing View 2 (does it really makes all its objects null when dismissed?)
2. How to append that string to the last index of a NSMutableArray?
This is a very good question that I also had trouble figuring out when I started coding for the iOS. Basically, you don't need to initialize a new view1 because the tabbar controller already holds the view1 object in its viewControllers property. Also, alloc/init'ing the string in not necessary in this situation.
Therefore, you would want to change this:
View1 *view1 = [[View1 alloc] initWithNibName:#"View1" bundle:nil];
view1.string = [[NSString alloc] initWithFormat:#"%#", TextField.text];
To something like this:
View1 *view1 = [self.tabbarController.viewControllers objectAtIndex:0];
view1.string = textField.text;
Or even:
((View1 *)[self.tabbarController.viewControllers objectAtIndex:0]).string = textField.text;
Part 2:
How to append that string to the last index of a NSMutableArray?
NSMutableArray *someArray = [[NSMutableArray alloc] init];
[someArray addObject:string];
[someArray addObject:#"anotherString"];
The answer from #chown will definitely work if the ViewController you're sending the string to is the base controller of a tabBarController.
If you were several levels deep into a NavigationController stack, then you'll need a different approach.
The approach I'd recommend would be to create a protocol. This is where you create a delegate of view2 to pass the string back down the stack before the view is dismissed.
There are loads of examples of this code both in the Apple Documentation and on the Internet (StackOverflow included) but here's a quick run down...
In View2.h
#import <UIKit/UIKit.h>
//define the protocol, so you can set the delegate to this type
#protocol View2Delegate;
#interface View2 : UIViewController
//other properties etc
#property (assign) id <View2Delegate> delegate;
#end
//put the actual protocol definition here so we can pass a reference to ourself back up too if needed...
#protocol View2Delegate
- (void)view2:(View2*)view passingStringBack:(NSString *)stringToPassBack;
#end
In View2.m you can call that delegate method where ever you like but here's an example:
- (void)viewWillDisappear:(BOOL)animated
{
if(self.delegate)
[self.delegate view2:self passingStringBack:#"String I'm passing back"];
[super viewWillDisappear:animated];
}
Then in View1.h
#interface View2 : UIViewController <View2Delegate>
and View1.m
- (void)view2:(View2*)view passingStringBack:(NSString *)stringToPassBack
{
NSLog(#"%#", stringToPassBack);
}
Another option would be to post a notification, but that is more a broadcast scenario than a targeted message so I won't bother posting example code for that.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Passing Data between View Controllers
I have two view controllers and I want to get some information from the previous view in my app. e.g:
In my app I go from the first page to the second. Depending on what button the user pushes, I want to change the info on the second screen. What's the fastest and easiest way to do this? I tried importing the class and rebuilding but that recreates the string obj and doesn't keep the info that I want.
Well there are (at least) two possibilities:
Add a property to the next view controller, do something like
NewVC *vc = [[NewVC alloc] init]; // or initWithNibName...
[vc setMyInformation:information];
Create a custom init method:
NewVC *vc = [[NewVC alloc] initWithMyInformation:information andNibName:#"nibName" bundle:nil]; // well you should get the point...
In your second ("child") view controller, keep a property for the string (see section 9).
When you instantiate the second view controller and before you push it onto the stack from the first view controller, set the string property's value, e.g., retain the first controller's string:
mySecondViewController.infoString = myFirstViewController.infoString;
Make sure your second view controller manages the memory for the string (usually with a release message in the controller's dealloc method, assuming your property is defined with a retain).
A second option is to keep properties in your application delegate, or another singleton that manages data for the application. But the first approach is a bit more lightweight.
If I understand you correctly, what you need is to create an instance variable in vc2. And then when you create an instance of vc2 from vc1 you can access that iVar to assign value, etc. before you present vc2. Here is an example:
In ViewController2.h file:
#interface ViewController2
{
NSString *string2; //create an instance variable
}
#property (nonatomic, retain) NSString *string2;
In ViewController2.m file:
#implementation ViewController2
#synthesize string2;
In ViewController1.m file:
#implementation ViewController1
//ViewController2 *viewController2 = [[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil]; //one way to instantiate
viewController2.string2 = #"whatever string"; //here you assign the value to the instance variable string2 in viewController2
//[self.navigationController pushViewController:childController animated:YES]; //etc. it depend on how you present viewcontroller2
I have two view controllers, firstViewController and secondViewController. I am using this code to switch to my secondViewController (I am also passing a string to it):
secondViewController *second = [[secondViewController alloc] initWithNibName:nil bundle:nil];
second.myString = #"This text is passed from firstViewController!";
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
I then use this code in secondViewController to switch back to the firstViewController:
[self dismissModalViewControllerAnimated:YES];
All of this works fine. My question is, how would I pass data to the firstViewController? I would like to pass a different string into the firstViewController from the secondViewController.
You need to use delegate protocols... Here's how to do it:
Declare a protocol in your secondViewController's header file. It should look like this:
#import <UIKit/UIKit.h>
#protocol SecondDelegate <NSObject>
-(void)secondViewControllerDismissed:(NSString *)stringForFirst
#end
#interface SecondViewController : UIViewController
{
id myDelegate;
}
#property (nonatomic, assign) id<SecondDelegate> myDelegate;
Don't forget to synthesize the myDelegate in your implementation (SecondViewController.m) file:
#synthesize myDelegate;
In your FirstViewController's header file subscribe to the SecondDelegate protocol by doing this:
#import "SecondViewController.h"
#interface FirstViewController:UIViewController <SecondDelegate>
Now when you instantiate SecondViewController in FirstViewController you should do the following:
// If you're using a view controller built with Interface Builder.
SecondViewController *second = [[SecondViewController alloc] initWithNibName:"SecondViewController" bundle:[NSBundle mainBundle]];
// If you're using a view controller built programmatically.
SecondViewController *second = [SecondViewController new]; // Convenience initializer that uses alloc] init]
second.myString = #"This text is passed from firstViewController!";
second.myDelegate = self;
second.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second animated:YES];
[second release];
Lastly, in the implementation file for your first view controller (FirstViewController.m) implement the SecondDelegate's method for secondViewControllerDismissed:
- (void)secondViewControllerDismissed:(NSString *)stringForFirst
{
NSString *thisIsTheDesiredString = stringForFirst; //And there you have it.....
}
Now when you're about to dismiss the second view controller you want to invoke the method implemented in the first view controller. This part is simple. All you do is, in your second view controller, add some code before the dismiss code:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!"];
}
[self dismissModalViewControllerAnimated:YES];
Delegate protocols are EXTREMELY, EXTREMELY, EXTREMELY useful. It would do you good to familiarize yourself with them :)
NSNotifications are another way to do this, but as a best practice, I prefer using it when I want to communicate across multiple viewControllers or objects. Here's an answer I posted earlier if you're curious about using NSNotifications: Firing events accross multiple viewcontrollers from a thread in the appdelegate
EDIT:
If you want to pass multiple arguments, the code before dismiss looks like this:
if([self.myDelegate respondsToSelector:#selector(secondViewControllerDismissed:argument2:argument3:)])
{
[self.myDelegate secondViewControllerDismissed:#"THIS IS THE STRING TO SEND!!!" argument2:someObject argument3:anotherObject];
}
[self dismissModalViewControllerAnimated:YES];
This means that your SecondDelegate method implementation inside your firstViewController will now look like:
- (void) secondViewControllerDismissed:(NSString*)stringForFirst argument2:(NSObject*)inObject1 argument3:(NSObject*)inObject2
{
NSString thisIsTheDesiredString = stringForFirst;
NSObject desiredObject1 = inObject1;
//....and so on
}
I could be way out of place here, but I am starting to much prefer the block syntax to the very verbose delegate/protocol approach. If you make vc2 from vc1, have a property on vc2 that you can set from vc1 that is a block!
#property (nonatomic, copy) void (^somethingHappenedInVC2)(NSString *response);
Then, when something happens in vc2 that you want to tell vc1 about, just execute the block that you defined in vc1!
self.somethingHappenedInVC2(#"Hello!");
This allows you to send data from vc2 back to vc1. Just like magic. IMO, this is a lot easier/cleaner than protocols. Blocks are awesome and need to be embraced as much as possible.
EDIT - Improved example
Let's say we have a mainVC that we want to present a modalVC on top of temporarily to get some input from a user. In order to present that modalVC from mainVC, we need to alloc/init it inside of mainVC. Pretty basic stuff. Well when we make this modalVC object, we can also set a block property on it that allows us to easily communicate between both vc objects. So let's take the example from above and put the follwing property in the .h file of modalVC:
#property (nonatomic, copy) void (^somethingHappenedInModalVC)(NSString *response);
Then, in our mainVC, after we have alloc/init'd a new modalVC object, you set the block property of modalVC like this:
ModalVC *modalVC = [[ModalVC alloc] init];
modalVC.somethingHappenedInModalVC = ^(NSString *response) {
NSLog(#"Something was selected in the modalVC, and this is what it was:%#", response);
}
So we are just setting the block property, and defining what happens when that block is executed.
Finally, in our modalVC, we could have a tableViewController that is backed by a dataSource array of strings. Once a row selection is made, we could do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selectedString = self.dataSource[indexPath.row];
self.somethingHappenedInModalVC(selectedString);
}
And of course, each time we select a row in modalVC, we are going to get a console output from our NSLog line back in mainVC. Hope that helps!
hmm, look for the notification centre and pass back info in a notification. here is apples take on it
- I take this approach personally unless any one has any other suggestions
Define a delegate protocol in the second view controller and make the first one the delegate of the second.
I have been wondering which is the best way to load a navigation view. I have found that there are 3 ways I can do it without having major errors
What I was wondering is which one is best for memory and as a recommended practice ??
1)
no declaration in .h file (the code below IS NOT writen in the .h file)
#interface companyViewController : UIViewController {
EmployeeViewController *employeeDetailViewController;
}
#property (nonatomic, retain) EmployeeViewController *employeeDetailViewController;
then no #syntesize in .m file, no release in dealloc and no nil in viewDidUnload and when I call the new view I do:
EmployeeViewController *employeeController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:employeeController animated:YES];
[employeeController release];
2)
I create it in the .h file (the code below IS written in the .h file)
#interface companyViewController : UIViewController {
EmployeeViewController *employeeDetailViewController;
}
#property (nonatomic, retain) EmployeeViewController *employeeDetailViewController;
then I #syntesize in .m file, with a release in dealloc and a nil in viewDidUnload and when I call the new view I do:
EmployeeViewController *employeeController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
employeeDetailViewController = employeeController;
[self.navigationController pushViewController:employeeController animated:YES];
[employeeController release];
3)
I do like 2 but I call the new view like this
employeeDetailViewController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:employeeController animated:YES];
I feel like #3 is wrong because from what I understand in the memory management, I allocate it once in the #property (nonatmoic, retain) and I also retain it when I alloc it when I decide to call it. This will make the view have a retain count of 1 and lead to leaks.
To make sure I do not create an excessive amount of new views and get EXC_BAD_ACCESS or memory leaks, which one should be best ?
Thanks for the help
In none of these examples are you actually using the property you declare. I'll go through them in turn.
You created a property called employeeDetailViewController, but you never synthesize it or store anything in it. Your view controller is only ever stored in a local variable before passing it off to the navigation controller.
You create an instance variable called employeeDetailViewController, and a property also called employeeDetailViewController. However, you never store anything in the property, you only access the instance variable directly. Since you don't retain the view controller (it doesn't happen automatically when you use instance variables), you've got a situation where you might over-release.
This one will actually crash. You told it to pushViewController:employeeController without having defined employeeController.
So, let's consider what's right. There are two issues here: firstly, how to use properties, and secondly, whether you need an instance variable/property to refer to the view controller at all.
Considering properties:
To access a property, you use self.propertyName. If you just use propertyName directly, you're writing directly into the instance variable, and the memory management stuff (like automatically retaining stuff that's put in the property) is completely bypassed. Generally, you should only ever do that in your init or dealloc method: everywhere else you should access the property properly by self.propertyName.
Do you need an instance variable/property for the view controller?
I would say you don't actually need an instance variable or property for the view controller you're pushing. Once it's been handed off to the navigation controller, in general you won't need to access it again. My version of your code would be:
aViewController = [[EmployeeViewController alloc]
initWithNibName:#"EmployeeViewController" bundle:nil];
[self.navigationController pushViewController:aViewController animated:YES];
Unless you're wanting to refer to that particular view controller from elsewhere in your code, this is all you need. Nothing in the header, no property anywhere.
Property access is more like
self.employeeDetailViewController = [[[EmployeeViewController alloc] initWithNibName:#"EmployeeViewController" bundle:nil] autorelease];
And then,
-(void)dealloc {
self.employeeDetailViewController = nil;
[super dealloc];
}