Storyboard is creating multiple instances of my DetailViewController - iphone

I started using storyboards but am noticing one very significant difference: The storyboard appears to be instantiating a new ViewController each time I navigate back and forth.
Example: I create two new Xcode projects based on the Master-Detail template. In Case 1, I use the Storyboard and in Case 2 I use the .xib.
Normally I would expect these to behave identically, but they don't!
In both of the DetailViewController.m I add the following method:
-(void)viewDidAppear:(BOOL)animated{
if (xposition ==0) {
xposition=50;
}else{
xposition = xposition+50;
}
NSLog(#"xposition update %d", xposition);
}
(I have also declared the xposition as an "int" instance variable in the header):
When I run the Storyboard version and tap the "+" and navigate in and out of the DetailViewController then my NSLog statement keeps giving me "xposition update 50".
By contrast, for the .xib version I get my expected behavior where each time I navigate in and out of the DetailViewController that the "position" increments by 50: so 50, 100, 150 etc.
How do I fix Storyboard to make it behave in the same way as the .xib based version. Specifically, I want to only instantiate the DetailViewController once.
EDIT: Answering my own question. I got some help on this and wanted to post the answer that worked for me.
When you first perform the segue store the destination viewcontroller in a property (see method "PrepareForSegue". My VC is called MyViewController)
Then create the delegate method called "shouldPerformSegueWithIdentifier" and use this to intercept the segue and manually present the stored ViewController for all subsequent segues.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
UIViewController *destination = segue.destinationViewController;
NSLog(#"identifier = %#", [segue identifier]);
if([[segue identifier] isEqualToString:#"mySegue"]) {
self.myViewController = (MyViewController*)destination;
NSLog(#"Saving myViewController for later use.");
}}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if([identifier isEqualToString:#"mySegue"]) {
if(self.myViewController != nil) {
NSLog(#"Using the saved myViewController.");
[self.navigationController pushViewController:self.myViewController animated:YES];
return NO;
}else {
return YES;
}
}
return YES;}

When you navigate back and forth your storyboard pops off your DetailViewController. Because it is not retained by anything else it will be released, this is normal behavior.
If you want to keep the instance you'll have to retain it in the ViewController you are calling it from and use it later on again. Check this question for an example
Edit:
I think you solved the problem but here is an example:
Create a property for you viewcontroller in your interface, say myViewController
Retain the viewcontroller in the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
[self setMyViewController:[segue destinationViewController]];
}
This it not leaking memory, your example could leak in some cases. Check out this guide here.
The next time the seque will be performed check if the property is already set and if so use it:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if([self myViewController] != nil){
[[self navigationController] pushViewController:[self myViewController] animated:YES];
return NO;
}else{
return YES;
}
}

Related

how to pass information between scenes objective c

I'm completely new to objective c and ios development, I'm simply trying to navigate to a new scene on my storyboard, and upon navigating change the text of a UIlabel to something.
The way I have this set up is I have a View controller and a tableview controller, each with their own header and implementation files. I'm using a segue from a button on the view controller to navigate to the tableview.
I did read this stack overflow post (How do I pass information between storyboard segues?) which explains how to pass values on a segue and have implemented it as such:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSLog(#"prepareForSegue: %#", segue.identifier);
if([segue.identifier isEqualToString:#"navtotablebut"])
{
TableViewController *testcontroller = segue.destinationViewController;
testcontroller.testpass.text = #"Testing123";
NSLog(#"%#",[NSString stringWithFormat:#"testpass has been changed to %#", testcontroller.testpass.text]);
}
}
The logs verify that this segue method does get called, and it is able to correctly identify the id I gave it (navtotablebut). I then attempt to set the value of my UIlabel's text property on my tableview controller to a simple string "Testing123". However not only does this change not reflect on the new screen, but my print statement afterwards seems to think testcontroller.testpass.text is equal to (null).
Am I doing something horribly wrong here?
You are setting the value of label testcontroller in TableViewController before the view of `TableViewController ' is drawn.
Please note: update the view in viewdidLoad or viewDidLayoutSubviews if using Autolayout.
Best way is pass your value as a string to other view controller and then populate.
Simply grab a reference to the target view controller in prepareForSegue: method and pass any objects you need to there. Here's an example...
- (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"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
}
}
also with performSegueWithIdentifier:sender: method to activate the transition to a new view based on a selection or button press.
// When any of my buttons are pressed, push the next view
-(IBAction)buttonPressed:(id)sender
{
[self performSegueWithIdentifier:#"MySegue" sender:sender];
}
// This will get called too before the view appears
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"MySegue"]) {
// Get destination view
SecondView *vc = [segue destinationViewController];
// Get button tag number (or do whatever you need to do here, based on your object
NSInteger tagIndex = [(UIButton *)sender tag];
// Pass the information to your destination view
[vc setSelectedButton:tagIndex];
}
}
In TableViewController, You need to create property,
TableViewController.h
#property (nonatomic, strong) NSString *subject;
you pass value like that,
In ViewController,
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier]isEqualToString:#"Detail"])
{
TableViewController *testcontroller = segue.destinationViewController;
testcontroller.testpass.text = #"Testing123";
detailView.subject = testcontroller.testpass.text;
}
}

calling a specific method when viewcontroller is resigned

I have two view controllers: RootViewController and DetailViewController
I define a Push Segue to DetailViewController from RootViewController in Storyboard, and I set its ID to ShowDetailView. I can then set up various variables in DetailViewController using the method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"ShowDetailView"]) {
}
}
My Question: Is there a way to call a method (performed in RootViewController) when I resign from DetailViewController back to RootViewController? I'm using the following code to resign:
[self.navigationController popViewControllerAnimated: YES];
By default? No. You can build a delegate for this.
But instead, I think you can perform the code you want inside -(void)viewWillAppear:(BOOL)animated of your RootViewController.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self myMethodHere];
}
The viewWillAppear method will be called exactly after your DetailViewController will be resigned.
EDIT:
Like a said, no easy way to do this. Maybe the best way its really with a delegate. It really has to be AFTER the resign? If it can be before, you can get the reference of your rootViewController and execute your method BEFORE the resign, like this:
RootViewController *rootViewController = (RootViewController*) [[self.navigationController viewControllers] objectAtIndex:0];
[rootViewController someMethod];
[self.navigationController popViewControllerAnimated: YES];
If you really want to be after, than you can create a flag and set to yes with the code above, and check this flag with an if inside viewWillAppear to execute your method, otherwise you cannot run from delegate.

How to check which segue was used

I got two segue's which lead to the same viewController. There are 2 buttons which are connected to the same viewController using 2 segues. In that viewController I need to check which button was clicked. So actually I need to check which segue was used/preformed. How can I check this in the viewControllers class? I know there is the prepareForSegue method, but I cannot use this for my purpose because I need to put the prepareForSegue in the class where the 2 buttons are, and I don't want it there but I want it in the viewControllers class because I need to access and set some variables in that class.
You need to set a variable of the second viewcontroller in the prepareforsegue method of first one. This is how it is done:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:segueIdentifier1])
{
SecondViewController *secondVC = (SecondViewController *)segue.destinationViewController;
if(sender.tag == ...) // You can of course use something other than tag to identify the button
{
secondVC.identifyingProperty = ...
}
else if(sender.tag == ...)
{
secondVC.identifyingProperty = ...
}
}
}
Then you can check that property in the second vc to understand how you came there. If you have created 2 segues in the storyboard for 2 buttons, then only segue identifier is enough to set the corresponding property value. Then code turns into this:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:segueIdentifier1])
{
SecondViewController *secondVC = (SecondViewController *)segue.destinationViewController;
secondVC.identifyingProperty = ...
}
else if([segue.identifier isEqualToString:segueIdentifier2])
{
SecondViewController *secondVC = (SecondViewController *)segue.destinationViewController;
secondVC.identifyingProperty = ...
}
}
So firstly you need to set your segues identifier directly in storyborads or through your code using the performSegueWithIdentifier method.
Independently the way you choosed, your view controller will fire the following method, so you need to override it to know which segue was sending the message, you do like this:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ButtonSegueIdentifierOne"]) {
// button 1
}
if ([segue.identifier isEqualToString:#"ButtonSegueIdentifierTwo"]) {
// button 2
}
}

Error setting NSNumber property of destination view controller in prepareForSegue

I have a modal view controller that attempts to set an flag (an NSNumber property) of the source view controller that called it in its prepareForSegue method. It fails to build with the error "No known instance method for selector 'setGoToEditNewNote:'". Here is the code:
Source View Controller .h:
#property (strong, nonatomic) NSNumber *goToEditNewNote;
Source View Controller .m:
#synthesize goToEditNewNote;
...
- (void)viewDidLoad
{
[super viewDidLoad];
// clear the flag
goToEditNewNote = [[NSNumber alloc] initWithBool:FALSE];
...
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if ([goToEditNewNote boolValue] == TRUE) {
goToEditNewNote = FALSE;
[self performSegueWithIdentifier: #"editNote" sender: self];
...
Modal View Controller .h:
Modal View Controller .m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"done"])
{
[self done];
[[segue destinationViewController] setGoToEditNewNote:TRUE]; <<< get error here
}
}
I suspect the problem may have to do with goToEditNewNote not being retained when the modal view is loaded, but i don't understand why not. I have set other properties such as the managedObjectContext in a similar way with success. Please be as specific as possible in your answer as I am a novice with ARC. Thanks - Tom
destinationViewController is of the id type which doesn't contain a goToEditNewNote property. You probably want to cast destinationViewController to the SourceViewController type. This is usually a warning but it sounds like you're treating all warnings as errors (I do this too).
Your -prepareForSegue:sender: should look something like this.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"done"])
{
[self done];
SourceViewController *sourceViewController = (SourceViewController *)[segue destinationViewController];
[sourceViewController setGoToEditNewNote:[NSNumber numberWithBool:YES]];
}
}
I had the same problem. It took me 2 days and could't find any right answer on the web. Strangely, the same code in Stanford CS193p is working. Luckily I solved it now! The "set property method can not be found" error's cause is forgetting to #import "xxxx.h", where xxxx is the name of your segue's destination view controller. If you dodn't import, it never knows what xxxx's properties are!
Easy, now the segue works.
At first if you are using ARC you NEVER have to use retain (or release, too). You only have to instantiate an Object, but you dont have to care about to delete it. But take care if Objects references in a circle at each other. If A references B, B references C, C references A and you access these Variables via a Variable D (wich points at A) and you no more use D the circle of A-B-C stays in Memory. Then you have to define one of the A-B-C References as 'weak'.
But to your real question: Is it possible that you missed the #synthesize Statement in the Implementation file?
The error is telling you that you don't have any instance method that corresponds to the "setGoToEditNewNote:" selector; did you #synthesize your property (or write the accessors yourself)?

performSegueWithIdentifier and sharedData

I hope I'm not asking something that's been already answered (but I found no answer to this, so hopefully I'm not).
I have an app in the current xcode version, using segues and navigationController. I need to pass data from one view to the other - what's the easiest way to do this? I ran onto some sharedData thing that could be possibly hooked onto the performSegueWithIdentifier method but don't know how to use it (or whether it is the right choice to do it like this).
Thanks
A segue has two view controllers: sourceViewController and destinationViewController. When UIKit executes a segue, it sends a prepareForSegue:sender: message to the source VC. You can override that method in your view controller subclass to pass data to the destination VC.
For example, suppose you have a master view controller with a table view of movies, and when the user clicks a row in the table view, you want to segue to a detail view controller for the movie.
#implementation MasterViewController
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DetailViewController *detailVC = segue.destinationViewController;
NSIndexPath *selectedPath = [self.tableView indexPathForSelectedRow];
detailVC.movie = [self movieForIndexPath:selectedPath];
}
This is explained in the Introducing Interface Builder Storyboarding video from WWDC 2011.
It's also worth noting that when the segue's origin is a table view cell, or the accessory button of a table view cell, the sender argument of prepareForSegue:sender: is the table view cell.
I think the best way is to import header for the view controller that will be shown in controller that is performing segue. And then use it's accessors or other methods to pass needed data inside prepareForSegue:
// In FirstViewController.h
#import "SecondViewController.h"
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"SegueToSecondViewController"]) {
// Get destination view controller and don't forget
// to cast it to the right class
SecondViewController *secondController = [segue destinationViewController];
// Pass data
secondController.dataArray = self.someDataArray;
secondController.name = #"Fancy name";
}
}
When you want data back from second to first, I suggest to use delegate:
// In FirstViewController.h
#import "SecondViewController.h"
#import "SecondViewControllerDelegate.h"
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"SegueToSecondViewController"]) {
SecondViewController *secondController = [segue destinationViewController];
// Declare first view controller as a delegate
secondController.delegate = self;
// Pass data
secondController.dataArray = self.someDataArray;
secondController.name = #"Fancy name";
}
}
// Second controller's delegate method,controller
// ie. used to return data after second view is dismissed
- (void)secondControllerFinishedSomeTask:(NSArray *)someReturnedData {
// Do something with returned data
}
When you want data back from second to first, better way is to use Unwind Segues.