Cannot access variable from another viewController - objective-c

I have one viewController called "setTimeViewController". I have another view controller called "setEventViewController." users tap a row in a table in setTimeViewController and are sent to setEventViewController that only contains a UIPickerView and a save button. When the user taps the save button, it takes them back to setTimeViewController.
I want the value that is chosen in that picker from setEventViewController to be able to be accessed from setTimeViewController but is returning (null) to me. I have declared a NSString *theVariable in .h setEventViewController which is the variable I am trying to retrieve from the other view controller and retained its property but it is still null.
I have done a test (NSLog the variable) from viewDidDisappear in setEventViewController to see if it is null when the view is disappearing but it works as it should.
Here is my code, if anyone can help me I would forever be grateful. Thank you!
setEventViewController.m
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
//[theVariable retain]; //Tried this as well but did not work
}
-(IBAction) Save
{
//simply dismisses the view controller back to setTimeViewController. Have also tried to set another NSString equal to theVariable but this did not work.
[self.navigationController popViewControllerAnimated:YES];
}
setTimeViewController
-(void) retrieveTheEvent
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
NSString *testString= eventViewController.theVariable;
NSLog (#"the event is %#", testString); //shows null
}

You are allocating different object of setEventViewController in retrieveTheEvent if I am not wrong. You are facing this problem because this newly allocated object is different than you have pushed.
Instead of use the same object that you have push to navigation controller.
One solution:
Create global object your setEventViewController(i.e. I mean create iVar for it) and use same reference to push view controller in didSelectRow. And use same iVar for accessing your theVariable.
Add below code in setTimeViewController.h
setEventViewController *eventViewController;
Please also create property for it.
Now in setTimeViewController.m
Now use existing reference of setEventViewController to push view controller. like
eventViewController= [[setEventViewController alloc] init];
[self.navigationController pushViewController: eventViewController animated:YES];
Change this method
-(void)retrieveTheEvent
 {
  NSString *testString= eventViewController.theVariable;
  NSLog (#"the event is %#", testString); //shows null
  }

Adding another solution to Armaan's list.
Create a delegate in setEventViewController.m and pass "theVariable" to setTimeViewController.m before calling
[self.navigationController popViewControllerAnimated:YES];

I'm giving an example.
setEventViewController.h
#protocol setEventViewControllerDelegate;
#interface setEventViewController : UIViewController
{
NSString* theVariable;
id<setEventViewControllerDelegate> delegate;
}
#end
#protocol setEventViewControllerDelegate <NSObject>
#optional
-(void)theVariableChanged:(NSString*)theNewValue;
#end
setEventViewController.m
#synthesize delegate;
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
// this is where the new value is passed to setTimeViewController
if(delegate && [delegate respondsToSelector:#selector(theVariableChanged)])
{
[delegate theVariableChanged:theVariable];
}
}
-(IBAction) Save
{
[self.navigationController popViewControllerAnimated:YES];
}
-(void) dealloc
{
// very important
self.delegate = nil;
}
setTimeViewController.h
#import "setEventViewController.h"
#interface setTimeViewController : UIViewController <setEventViewControllerDelegate>
{
// your members
}
#end
setTimeViewController.m
-(void)openSetEventView
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
// set the delegate
eventViewController.delegate = self;
[self.navigationController pushViewController: eventViewController animated:YES];
[eventViewController release];
}
// get the new value here
-(void)theVariableChanged:(NSString*)theNewValue
{
NSLog (#"the event is %#", theNewValue);
}

Check out Singletons,
They can be your best friend when doing something like this.
Singletons link here

I tried all different suggestion to pass NSString from one viewController to the other, including customer initialise method, and change the property to strong, retain, non of them work for me.
At last singleton to share the data across the project fixed the problem. http://www.galloway.me.uk/tutorials/singleton-classes/
Thanks #David Evans

Related

Using dealloc method to release

I'm trying to understand memory management to do some better apps, but was stopped at one point :
I use some UIButtons. So I alloc them, work with them etc. But i need to release them at one moment.
So I implement deallocmethod for all the object which are usefull all the time the UIViewController is on screen, and which need to be released when it desappeard. So in my UIViewController I implement :
-(void)dealloc
{
NSLog(#"Dealloc call");
[myButton release];
.... //Some objects release
[super dealloc];
}
But i never see the Dealloc call printed, so I think that it doesn't passed by the dealloc method when the UIViewController desappeard.
So, how does it work ? / What is false ?
Thanks !
EDIT : method to change of viewController :
-(void)myMethod
{
if (!nextviewcontroller)
{
nextviewcontroller = [[NextViewController alloc]init];
}
UIView *nextView = nextviewcontroller.view;
UIView *actualView = actualviewcontroller.view;
[actualviewcontroller viewWillAppear:NO];
[nextviewcontroller viewWillDisappear:NO];
[actualView removeFromSuperview];
[self.view addSubview:nextView];
[actualviewcontroller viewDidAppear:NO];
[nextviewcontroller viewDidDisappear:NO];
}

Does a view reset all its variables every time it opens?

I have a int that resets itself every time the view re-opens/leaves. I have tried every way of declaring the int that i can think of, from public, to instance variable to global variable, but it still seems to reset!
#interface MainGameDisplay : UIViewController
extern int theDay;
#implementation MainGameDisplay
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"%i", theDay);
}
- (IBAction)returnToHome:(id)sender {
ViewController *new = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
- (IBAction)theDayAdder:(id)sender {
theDay++;
}
Okay so theDay is a global integer variable. on View load NSLog returns an output of 0. I can then click theDayAdder as many times as I want, and when I click returnToHome, it will tell me what theDay is. When I come back to MainGameDisplay page however, theDay will be reset back to zero, even though it is a global variable?
Output:
0
N (number of times you clicked 'theDayAdder' button)
0
The problem is that you alloc init'ing a new instance of MainGameDisplay every time you go back to it, so of course your global variable will be reset to 0. You need to create a property (typed strong) in ViewController, use that to go back to the same instance each time.
- (IBAction)returnToGameDisplay:(id)sender {
if (! self.mgd) {
self.mgd = [[MainGameDisplay alloc] initWithNibName:nil bundle:nil];
}
[self presentViewController: self.mgd animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
In this example mgd is the property name created in the .h file.
You should know that viewDidLoad() is called when the view is loaded--not when when the view "opens" as you say. You might have a view opened in a retained value and re-opened time and time again and have vieDidLoad() called only once. However, whenever the view becomes visible, then viewWillAppear() is the delegate that is called. So, try outputting your value in viewWillAppear()--instead of viewDidLoad() and call the view appropriately (i.e., have it stick around and not created every time you need it). This will keep the view from being destroyed between calls. The code for your view should look like the following:
#interface MainGameDisplay : UIViewController
extern int theDay;
#implementation MainGameDisplay
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
NSLog(#"%i", theDay);
}
- (IBAction)returnToHome:(id)sender {
ViewController *new = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
NSLog(#"%i", theDay);
}
- (IBAction)theDayAdder:(id)sender {
theDay++;
}
The parent of the view (I assume the appDelegate) should do the following
#property (nonatomic, strong) MainGameDisplay *mainGameDisplay = [[MainGameDisplay alloc] initWithNib:#"MainGameDisplay" …]
ViewDidLoad() is called once--after the view is created and loaded. However, viewWillAppear() and other functions triggered by IBAction etc. are called appropriately.
extern variables are meant to be constant. If you expect your MainGameDisplay class to be long-lived, or if theDay is otherwise only supposed to be tied to that class, why not either declare theDay as a property, or, if you only ever need to set it internally in MainGameDisplay, as an ivar.
The other alternative, if you want that value to continue to exist independently of the class instance where it's declared, is to declare it static. A static var will retain its value, even across the lifetime of different instances of the class where it's declared.

It is possible to use an existing ViewController with PerformSegueWithIdentifier?

I use the method performSegueWithIdentifier:sender: to open a new ViewController from a storyboard-file programmatically. This works like a charm.
But on every time when this method is being called, a new ViewController would be created. Is it possible to use the existing ViewController, if it exista? I don't find anything about this issue (apple-doc, Stack Overflow, ...).
The Problem is:
On the created ViewController the user set some form-Elements and if the ViewController would be called again, the form-elements has the initial settings :(
Any help would be appreciated.
Edit:
I appreciate the many responses. Meanwhile, I'm not familiar with the project and can not check your answers.
Use shouldPerforSegueWithIdentifier to either allow the segue to perform or to cancel the segue and manually add your ViewController. Retain a pointer in the prepareForSegue.
... header
#property (strong, nonatomic) MyViewController *myVC;
... implementation
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if([identifier isEqualToString:#"MySegueIdentifier"]){
if(self.myVC){
// push on the viewController
[self.navigationController pushViewController:self.myVC animated:YES];
// cancel segue
return NO;
}
}
// allow the segue to perform
return YES;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"MySegueIdentifier"]){
// this will only be called the first time the segue fires for this identifier
// retian a pointer to the view controller
self.myVC = segue.destinationViewController;
}
}
To reuse an existing UIViewController instance with a segue create the segue from scratch and provide your own (existing) destination (UIViewController). Do not forget to call prepareForSegue: if needed.
For example:
UIStoryboardSegue* aSegue = [[UIStoryboardSegue alloc] initWithIdentifier:#"yourSegueIdentifier" source:self destination:self.existingViewController]
[self prepareForSegue:aSegue sender:self];
[aSegue perform];
Following code makes singleton view controller.
Add them to your destination view controller implementation, then segue will reuse the same vc.
static id s_singleton = nil;
+ (id) alloc {
if(s_singleton != nil)
return s_singleton;
return [super alloc];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
if(s_singleton != nil)
return s_singleton;
self = [super initWithCoder:aDecoder];
if(self) {
s_singleton = self;
}
return self;
}
I faced this problem today and what I have done is to create the view controller manually and store it's reference.
Then every time I need the controller, check first if exists.
Something like this:
MyController *controller = [storedControllers valueForKey:#"controllerName"];
if (!controller)
{
controller = [[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:NULL] instantiateViewControllerWithIdentifier:#"MyControllerIdentifierOnTheStoryboard"];
[storedControllers setValue:controller forKey:#"controllerName"];
}
[self.navigationController pushViewController:controller animated:YES];
Hope it helps.
Create a property for the controller.
#property (nonatomic, weak) MyController controller;
And use some kind of lazy initialization in performSegueWithIdentifier:sender
if (self.controller == nil)
{
self.controller = [MyController alloc] init]
...
}
In this case, if controller was already created, it will be reused.
Firstly you would be going against Apple's design in Using Segues: "A segue always presents a new view controller".
To understand why it might help to know that what a segue does is create a new view controller and then the perform calls either showViewController or showDetailViewController depending on what kind of segue it is. So if you have an existing view controller just call those methods! e.g.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
Event *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
[self showDetailViewController:self.detailViewController.navigationController sender:self];
}
You would need to make the Viewcontroller into a singleton class.

Memory managment question

I have a button with IBAction, which shows another window:
-(IBAction)someButtonClick:(id)sender
{
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
[anotherView showWindow:self];
}
I worry about memory management in here. I allocate an object in this IBAction and don't released it. But how can i do it? If i released this object after showing, window will closing immediately.
The view is stored in an instance variable and you have access to it anywhere in your class. Release it in the code that dismisses the view.
Since anotherView is an instance variable you can release it in your dealloc method. But then you still have a memory leak, since every time your button is clicked a new instance of the window controller is created, but only the last one can be freed. You really should use accessors for this. Here is my suggestion:
- (NSWindowController *) anotherView;
{
if (nil == anotherView) {
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
}
return anotherView;
}
- (void) setAnotherView: (NSWindowController *) newAnotherView;
{
if (newAnotherView != anotherView) {
[anotherView release];
anotherView = [newAnotherView retain];
}
}
- (void) dealloc;
{
[self setAnotherView: nil];
[super dealloc];
}
- (IBAction) someButtonClick: (id) sender;
{
[[self anotherView] showWindow: self];
}
If you use a Objective-C 2.0 property you don't have to write the setter.
And also you should rename your instance variable, the name should reflect what it is. And a View is not a Window Controller.

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.