In my app, I use a storyboard and segues, and I often pass data to the destination view controller before doing the segue, as follows:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController respondsToSelector:#selector(setMyData:)]) {
[segue.destinationViewController performSelector:#selector(setMyData:)
withObject:myData];
}
}
It works everywhere except in one place. The selector gets called, the data gets set, but when the segue completes and the destination controller appears, it doesn't have the data I just set. After printing the view controller's id in both the source and destination view controllers, I found that the segue.destinationViewController in the code above is a DIFFERENT instance of the view controller than the one that gets displayed. What's going on here?
[UPDATE 1]
I looked into the lifecycle of the destination view controller, and it first gets loaded during the segue execution, but AFTER I set the property on it! This means, that when I call performSelector on it, the view controller object is not initialized! This is why the data I set doesn't stick. t don't understand why is this the case, and why this same approach works in the other parts of my app.
[UPDATE 2]
Posting the code of setMyData by request. At first I didn't have this method at all, because locationToOpen is a public property. I only added it to ensure it gets called and to print the debug info.
- (void)setMyData:(MyData *)myData
{
DLog(#"self: %#", (id)self);
_myData = myData;
}
I would do it as follows -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"NextSegue"])
{
DestinationViewController *dest = [segue destinationViewController];
dest.myData = self.myData;
}
}
And in the DestinationViewController.h, you should create a property -
#import <UIKit/UIKit.h>
#interface DestinationViewController : UIViewController
#property (nonatomic, strong) NSObject *myData;
#end
And also, make sure to synthesize the myData property in DestinationViewController.m -
#interface DestinationViewController ()
#end
#implementation DestinationViewController
#synthesize myData = _myData;
// other methods here
#end
I had this same issue. It turned out for me that the target ViewController property I was setting in my prepareForSegue: code was declared as weak because I had copied and pasted the property from one that InterfaceBuilder auto-created, but mine was not a Storyboard object. So my property was being released and zeroed by ARC on exit from prepareForSegue:. Making it a non-weak property fixed it.
I had a similar problem where the ViewController I was doing the segue to changed at some odd point,
after a looking around a bit it seems that the segue created a new ViewController.
To solve the data passing problem I used notifications.
I know this thread is old and solution should be found. But since the final solution is not posted here, I would like to list one of the possible root cause (which is the one in my case). I had the same issue, trying to set the variable in the destination view controller of the segue. The root cause is that I forgot to instantiated ([[Class alloc]init]) the object variable. I need it in my case because I am setting the properties of the object instead of pointing to other object. Hope this help.
Related
I'm stuck with a problem which I'm not sure how to solve. I have several view controllers which follows each other as the user populates them with data.
Before changing views I use an alert controller for the user to confirm correctness of data before pushing to the next view controller. However, here I need to capture the data but I'm only writing the data to Realm once all the data is gathered.
My question is this; how do I temporarily keep this data (and use some of them forward) until it's time to write to the database?
The easiest way is to create a property in each of the view controllers.
in the .h file, under interface
#property (strong, nonatomic) MyRealmObject *realmObject;
in the .m file, under implementation
#synthesize realmObject
Then, in your prepareForSegue method, you populate the copy local to your view controller, instantiate the destination view controller, and then pass the local Realm object into the property of the destination view controller.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"SegueName"]) {
DestinationViewController *destViewController = segue.destinationViewController;
destViewController.realmObject = self.realmObject;
}
}
This will pass a copy of your existing realm object to the next view controller
I have two views. The main view is ViewController and the next is AddItemViewController. ViewController has a tableview that lists items that you add when you go to AddItemViewController. There is a button on AddItemViewController that segues back to ViewController. The problem is, upon returning to ViewController expecting that an item be added, the private data of ViewController is suddenly set to nil. I have lost data and any chance to interact with my objects after returning from the segue.
Here is the data that's getting set to nil
#property (strong, nonatomic) costEstimator *myCost;
#property NSString *testString;
What am I doing wrong?
Here is my prepareforsegue code in the AddItemViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
ViewController *vC = [segue destinationViewController];
[vC addSomething:_selectedItem withQuantity:[_quantBox.text doubleValue]];
}
Any help is greatly appreciated. Thanks :)
You want to pop the AddItemViewController in this case. When you segued from ViewController to AddItemViewController, I am guessing you did a push. What this does effectively is it adds AddItemViewController ontop of ViewController in the memory stack. By 'segue-ing' again from AddItemViewController to ViewController, you are adding ANOTHER ViewController instance ONTOP of AddItemViewController. This is why you think you are losing your data when in actuality, you aren't. You are only seeing the wrong view controller.
I have been trying to have a better understanding on how to pass data between view controllers and everything is make more sense but there is one thing I would like to understand better.
I came across this thread/tutorial here at StackOverFlow (Passing Data between View Controllers), I tried it and it worked as expected, but the one thing I don't know understand is why we can change a BOOL property located in the second view controller but NOT a Label. In other words if I add a second property and try to change the label in the prepareForSegue: method it doesn't work, why?
In section Passing Data Forward using Segue's I tried adding a second property for a label in the second view controller, right where isSomethingEnabled is, like this...
#property(nonatomic) BOOL *isSomethingEnabled;
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
Than in prepareForSegue: method located in the first view controller I did this..
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.myLabel.text = #"Hello"; // here, no error but it doesn't update the label
}
}
Why you can modify a BOOL property located in a second view controller but not a label? Can someone explain this a little bit?
Thanks a lot
As Grzegorz Krukowski said the UIViewController isn't loaded at that moment. You can set #property values and access them in the viewDidLoad method and setup your Labels accordingly.
First of all, you should not do this. You should treat another view controller's views as private. The reason for this is the concept of encapsulation. A view controller's views are part of the view's appearance, not it's API. If at a future date you decide to refactor the VC so that it uses the text string in question as the title in a navigation controller, you should be free to do this. If another view controller reaches inside your view controller and manipulates it's view objects directly, you must keep those view objects unchanged forever, or you run the risk of breaking the way the other view controller interacts with it. That's bad, and 6 months from now you won't remember that 3 outside view controllers manipulate your view controller's views, and when you forget and change your views and decide to move a piece of text to a different UI object, your app will stop working correctly.
What you SHOULD do is to add a string property to your ViewControllerB, and set THAT in your prepareForSegue. Then in ViewControllerB's viewWillAppear:animated method, fetch the contents of that string property and install it in your label's text property.
Then the string property becomes part of your view controller's API contract (its public interface.) By adding a public string property to your interface, you guarantee that you will accept string values to that property and that you will do the right thing with them.
Now, as to why it doesn't work. A view controller's view hierarchy is not loaded until it's displayed. At the time of prepareForSegue, the view controller has been initialized, but it's views haven't been loaded yet. All its IBOutlets are nil. (this is another reason why you should not try to manipulate another view controller's views BTW)
Your prepareForSegue method looks like this: (wrong)
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.myLabel.text = #"Hello"; // here, no error but it doesn't update the label
}
}
Instead, your code should look like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.textToShow = #"Hello"; //This is the change
}
}
Then, in your ViewControllerB's viewWillAppear:animated method:
- (void) viewWillAppear: animated;
{
[super viewWillAppear: animated];
//install the text from our textToShow property into the label
self.myLabel.text = self.textToShow;
}
Odd issue here:
I created a storyboard with two view controllers, connected with a custom segue(a cross disolve). I make the segue happen by:
-(void)transitionToIntro
{
[self performSegueWithIdentifier:#"ToIntro" sender:self];
}
This transitions and I see the new view controller, After the initWithCoder and AwakeFromNib it sets up the UI and I see that the properties for data source and delegates are set. THen all of a sudden dealloc is fired! and this destination view controller is dealloc'd. Then of course everything is bunked. Delegates are nil and Datasources are nil because the containing viewcontroller is invalid to the subviews that contained it...
I dont know why this is happening, is the source viewcontroller not retaining a strong hold on the destination viewcontroller?
I have made sure to make the initial view controller set in the story board to my source view controller.
Any help is appreciated
Solution:
Make sure to have your source VC have a retained strong reference to the destination VC , and set that in prepareForSegue. If not you will see your destination VC dealloc itself after running initWithCoder and AwakeFromNib.
#property (nonatomic, strong) IntroViewController *destVC;
-(void)transitionToIntro
{
[self performSegueWithIdentifier:#"ToIntro" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToIntro"])
{
IntroViewController *dest = [segue destinationViewController];
self.destVC = dest;
}
}
I feel ignorant for asking this because I know it's simple. Goal: Retain a variable on a view after leave then returning to it.
For instance: Let's say we have MainView, CategoryView and (drumroll) ProjectView
Application opens to MainView it displays a table - user selects they want to pick a category. This segue's them to CategoryView. Once a selection is made I send the chosen category back to MainView. via
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
No problem here. MainView receives variable and displays the chosen category as subtext in the 'Category' section. Next the user wants to pick a project name (this is also a predefined list) They select the item and I send the variable back the MainView.
-- Now can somebody explain to me (gently) why when I return to the main view the NSString variable that was previously holding the 'chosen' category is now null?
So my NSString selectedProject is not being retained correct? What is the correct implementation I should be doing here? Or what am I missing? I'm really trying to understand whats going on so anything would be a great help.
MainView Interface
#interface MainViewController : UIViewController {
NSString *selectedProjectName;
NSString *selectedCategory;
}
#property (nonatomic, retain) NSString *selectedProjectName;
#property (nonatomic, retain) NSString *selectedCategory;
#end
MainView Implementation
#implementation MainViewController
#synthesize selectedCategory, selectedProjectName;
and if you need it..
ProjectView Implementation
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"masterSegue"]) {
MainViewController *vc = (MainViewController *)[segue destinationViewController];
LogValues *lv = [LogValues alloc];
lv.project = #"Test Project Name";
vc.selectedProjectName = lv.project;
}
}
Ok, your problem is that you're not returning to the same instance. You've missed one very very important thing about segues -- they ALWAYS instantiate new view controllers. So, when you use a modal segue to go "back" to the main view controller, you're just creating a new instance of that controller. You should never go backwards (to earlier controllers) in a storyboard using anything other than an unwind segue. Unwinds are the exception to the rule about segues always creating new instances.
So, you have 2 choices. You can either use an unwind segue, which is nice, because you can still implement prepareForSegue to send data back to the destination controller, or you can not use a segue at all, and use dismissViewControllerAnimated:completion: to go back, and use a delegate to send back the data.
So of course immediately after posting I see a thread in the side that I believe helps. The solution was to store the values in the AppDelegate which is working fine.
I feel however there are other ways to achieve the same result without having to rely on the AppDelegate and would love to hear other solutions.