I'm working on an app and I need to pass data between view controllers. I know this is a common question but I couldn't find an answer for my problem : I'm able to pass data from the FirstViewController (MasterViewController in my case) to the SecondViewController (SettingsViewController) but not the reverse. What happens is that I call a method from the FirstViewController in my SecondViewController.m file. This works and it logs the data. But when I quit the SecondViewController (using [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];) the data is reset.
I tried using other methods to pass data but it didn't work. I'm using this code to pass data:
MasterViewController *vc = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[vc setPorts:SelectedPorts];
I also tried replacing [vc setPorts:SelectedPorts]; with vc.selectedCellIndexes = SelectedPorts; but the same problem occurs.
the setPorts method is declared in the FirstViewController.h file and SelectedPorts is a variable I declared in SecondViewController.m (it's not nil I checked).
Here's the setPorts: in FirstViewController.m :
- (void) setPorts:(id)selectedPorts {
selectedCellIndexes = selectedPorts;
NSLog(#"selectedCellIndexes : %#", selectedCellIndexes);
}
This logs the good value but when I log it in viewWillAppear in FirstViewController.m it's reset to the value it has before I called the method from SecondViewController.m.
Just to clarify, if I DON'T quit the SecondViewController.m, the data isn't reset.
I did read all your comments, and I really thanks you for your help. for convenience, I used a global variable.
Thanks for your help.
You have a list of ports in MasterViewController and you expect to use it in the SettingsViewController.
The MasterViewController can hold this list and SettingsViewController should have an access to it.
In SettingsViewController, have a setSelectedPort method:
#property (nonatomic, retain) id selectedPorts
- (void) setPorts:(id)selectedPorts;
The method saves the selected ports list into a property.
In MasterViewController, call the SettingsViewController and give it the list.
SettingsViewController *vc = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[vc setSelectedPorts:yourValue];
When the list is modified inside the SettingsViewController, the list of ports contained in MasterViewController won't move even if you leave the SettingsViewController.
In secondViewController, You create protocol
#import <UIKit/UIKit.h>
#protocol sampleDelegate <NSObject>
- (void)passValue:(id)selectedPorts
#end
#interface SecondViewController : UIViewController
#property (nonatomic, strong) id <sampleDelegate> passDelegate;
#end
In viewDidLoad or wherever method as per your need, call method like this,
[self.passDelegate passValue:selectedPorts];
In FirstViewController.h,
Import the delegate <sampleDelegate>,
#import "SecondViewController.h"
#interface FirstViewController : UIViewController<SampleDelegate>
#end
In FirstViewController.m,
-(void)passValue:(id)selectedPorts
{
id receivedValues = selectedPorts;
}
and set self in your SecondViewController allocation,
SecondViewController *vc = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
vc.passDelegate = self;
There is nothing unusual in the getting result. By doing
MasterViewController *vc = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[vc setPorts:SelectedPorts];
You are creating a new instance of MasterViewController from your SecondViewController. This is not the same from which you navigated to the SecondViewController. So you wont get the expected result. Since you are setting the ports([vc setPorts:SelectedPorts]) to the newly created instance of the Master.
Instead of creating a new instance,just hold the reference of the MasterViewController in SecondViewController in a property and assign it before moving to second VC. As a beginner I suggested this way. But using delegate is the prefferred way passing data back.
Either use delegate methods to communicate with the master VC from the modal VC, or you could do something like this if you want to retrieive some manipulated objects from the modal VC.
Set the object(s) as properties in the modal view controller's .h-file (so they are public).
Using unwind segues, in the master VC, just do this:
-(IBAction)exitModalVC:(UIStoryboardSegue*)segue
{
SomeObject *obj = ((YourModalVC*)segue.sourceViewController).someObject;
//Do what you want with obj
}
EDIT:
This will only work if you are using unwind segue (which is a neat way of dismissing modal VC when using story board)
And you are using this, which is not unwind segues:
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
You were creating a new instance of the first view controller from the 2nd view controller not accessing the same instance of the original caller. That was the reason why while you could see the logs but data were not there when you got back to the original caller - your MasterViewController.
You need to use delegate method. Check my answer for this SO.
This is problem related to object ownership.
Follow the below steps:
As per understanding you want reverse value from "SecondViewController" to "FirstViewController"
Don't create new object of FirstViewController in SecondViewController, it will not work.
Create object of "FirstViewController" in "SecondViewController.h" file.
#property (nonatomic,strong) FirstViewController *firstViewController;
When you navigate from FirstViewController to SecondViewController, please pass the "self".
e.g. SecondViewController *vc = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
vc.firstViewController = self;
If you want pass the reverse value to FirstViewController then in SecondViewController.m file.
[self.firstViewController setPorts:SelectedPorts];
And in FirstViewController.m refresh your controls with latest values.
Try above code will defiantly work as per your requirement.
Related
I have a view that carries out a task, when the task is completed the activity indicator will become hidden, I want to send the user to another view when the activity is completed, or give the user an error if unsuccessful. So far I am using an If Else statement to give a success alert or an error alert. There is no buttons to click or anything, simply after the activity is completed the user will be sent to another view passing a few variables along the way.
How would I go about sending the user to another view after completion?
If you use a navigationcontroller:
[self.navigationController pushViewController:theNextViewController animated:YES];
For Storyboard, look up the method in the docu.
To elaborate on AlexWien's answer a little...
Create a view controller or table view controller class that you want
the user to go to after completion.
Create some properties for the data you want to pass in.
#protocol UpdatePricesDelegate;
#interface NXUpdatePricesViewController : UITableViewController
#property (strong, nonatomic) NSArray *calculationProducts;
#property (strong, nonatomic) NSArray *filteredCalculationProducts;
#property (weak, nonatomic) id<UpdatePricesDelegate>delegate;
#end
#protocol UpdatePricesDelegate <NSObject>
- (void)updatePricesController:(NXUpdatePricesViewController *)controller didUpdateCalculationProducts:(NSArray *)calculationProducts;
#end
When you are ready to display your controller (presumably in your If/Else statement), instantiate the class (don't forget #import "MyClassName.h") and set the properties to the variables you want to pass.
Present the class modally (example includes a navigation controller), or if you want to push the view, use your navigation controller.
NXUpdatePricesViewController *updatePricesController = [[NXUpdatePricesViewController alloc] initWithStyle:UITableViewStyleGrouped];
updatePricesController.delegate = self;
updatePricesController.calculationProducts = self.calculationProducts;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:updatePricesController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:navigationController animated:YES completion:nil];
NXCalculationViewController *calculationController = [[NXCalculationViewController alloc] init];
calculationController.calculation = calculation;
[self.navigationController pushViewController:calculationController animated:YES];
I actually got it working, along with passing a variable to the other view using this method:
DetailViewController *detail = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailView"];
[detail setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
detail.videoURL = outputURL;
How would I go about changing the a UILabel property in another view controller?
I have #import "SecondViewController.h" imported in the FirstViewController.m file and then
I have the following in a method in FirstViewController
-(IBAction) someAction {
SecondViewController *objV1 = [[SecondViewController alloc]init];
objV1.secondViewControllerLabel.alpha = 0.2;
NSLog(#"someAction");
}
when someAction is called nothing happens to the UILabel in the SecondViewController.
also, in this example both first and second view controllers are in another view controller called MainViewController. So, they are both onscreen at the same time.
thanks for any help.
From what you tell us, it would seem that you need to set the "embeded view controllers" as childs of the parent View Controller.
[mainViewController addChildViewController:childViewController];
[childViewController.view setFrame:self.view.bounds];
[self.childContainerView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
This is very powerful, because you can forward IBActions from the mainViewController to their child...
[mainViewController childViewControllers]
Returns an array of them, and also take a look at
– shouldAutomaticallyForwardRotationMethods
– shouldAutomaticallyForwardAppearanceMethods
So your child get automatically informed about the rotations of their parent.
To answer your question, you could do something like:
// In Parent View Controller
- (IBAction) anAction:(id) sender
{
for (CustomChildController *child in self.viewControllers) {
[child handleSomeAction];
}
}
Check out what the docs say for more details.
#Goles answer will work, but if you specifically want to trigger the change from FirstViewController.m you need to pass in a reference to SecondViewController somehow.
So you could do it with a custom init that takes a reference to your second viewcontroller as a parameter, or create a property on your FirstViewController that you can set from outside, which would be something like this:
FirstController.h:
#interface
..
#property (strong, nonatomic) UIViewController *second;
...
#end
FirstController.m:
#implementation
#synthesize second
In your parent ViewController you would create both the child view controllers, then:
ViewController1.second = ViewController2;
Then your action method would become:
-(IBAction) someAction {
self second.secondViewControllerLabel.alpha = 0.2;
NSLog(#"someAction");
}
Since in the secondViewController, secondViewControllerLabel has not been created yet, 'objV1.secondViewControllerLabel.alpha' will have no effect. Ideally, you should create a NSNumber property called labelAlpha in the secondViewController, set that property in the firstViewController, and then in the viewDidLoad of the second controller, add this line ::
self.secondViewControllerLabel.alpha = self.labelAlpha;
This will work for you.
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 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 asked this question earlier with way too much code.
The ViewController initializes a UIView chain, Controller>>View>>SubView, in the ViewController. After the SubView is initialized the ViewController is set as its delegate:
aSubView.delegate = self;
NSLog(#"$#",aSubview.delegate), returns the ViewController, so I know it is set.
In the SubView, NSLog(#"$#",self.delegate),returns random crap such a hr.lproj or a file path to the Foundation framework.
It crashes when attempting to implement any of the delegates methods, since the delegate doesn't link to the ViewController but instead randomness.
This is what the SubView.h file looks like:
#import "TestDelegate.h"
#interface TestSubView : UIView {
id<TestDelegate> delegate;
}
#property (assign) id<TestDelegate> delegate;
EDIT: ViewController is initialized in the app delegate as such:
ViewController *controller = [[ViewController alloc] init];
[window addSubview:controller.view];
[controller release];
The only other thing I added to the App Delegate, over the default is an import of the ViewController header
Is it possible the view controller object is being released/dealloced between the two calls to NSLog?