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.
Related
Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.
I want to create a similar class to UIAlertView which doesn't require a strong ivar.
For example, with UIAlertView, I can do the following in one of my UIViewController's methods:
UIAlertView *alertView = [[UIActionSheet alloc] initWithTitle:nil
message:#"Foo"
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[alertView show];
... and the actionSheet will not be dealloced until it is no longer visible.
If I were to try to do the same thing:
MYAlertView *myAlertView = [[MYAlertView alloc] initWithMessage:#"Foo"];
[myAlertView show];
... the myAlertView instance will automatically be dealloced at the end of the current method I am in (e.g. right after the [myAlertView show] line).
What is the proper way to prevent this from happening without having to declare myView as a strong property on my UIViewController? (I.e. I want myView to be a local variable, not an instance variable, and I would like the MYAlertView instance to be in charge of its own lifecycle rather than my UIViewController controlling its lifecycle.)
Update: MYAlertView inherits from NSObject, so it cannot be added to the Views hierarchy.
UIAlertView creates a UIWindow, which it retains. The alert view then adds itself as a subview of the window, so the window retains the alert view. Thus it creates a retain cycle which keeps both it and its window alive. UIActionSheet works the same way.
If you need your object to stay around, and nothing else will retain it, it's fine for it to retain itself. You need to make sure you have a well-defined way to make it release itself when it's no longer needed. For example, if it's managing a window, then it should release itself when it takes the window off the screen.
If you add it as a subview of another view it will be retained. When the user selects and action or dismisses it, then it should call self removeFromSuperview as it's last act.
I've done my own AlertView with a little trick.
Just retain the object himself and release it on action. With this, you can call your custom alert vies as native one.
#import "BubbleAlertView.h"
#import <QuartzCore/QuartzCore.h>
#interface BubbleAlertView ()
...
#property (nonatomic, strong) BubbleAlertView *alertView;
...
#end
#implementation BubbleAlertView
...
- (id)initWithTitle:(NSString*)title message:(NSString*)message delegate:(id)delegate cancelButtonTitle:(NSString*)cancelButtonTitle okButtonTitle:(NSString*) okButtonTitle
{
self = [super init];
if (self)
{
// Custom initialization
self.alertView = self; // retain myself
//More init stuff
}
return self;
}
...
//SHOW METHOD
- (void)show
{
// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];
// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;
}
- (IBAction)btPressed:(id)sender
{
//Actions done
[UIView animateWithDuration:0.4f animations:^{
self.vContent.alpha = 0.f;
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
self.alertView = nil; // deallocate myself
}];
}
You need to retain it somehow until it is released.
I do not really understand why you cannot implement it as subclass of UIView. Then you could use the view hierarchy as the keeper of a strong reference (retain +1). But you will have good reasons for not doing so.
If you don't have such a thing then I would use an NSMutableArray as class varialbe (meaning statc). Just declare it in the #interface block and initialize it with nil:
#interface
static NSMutableArray _allMyAlerts = nil;
provide an accessor.
-(NSMutableArray *) allMyAlerts {
if (_allMyAlerts == nil) {
_allMyAlerts = [[NSMutableArray alloc] init];
}
return _allMyAlerts
}
Within the init method do the following:
- (id) init {
self = [super init];
if (self) {
[[self allMyAlerts] addObject:self];
}
}
You will invode some method when the alert is dismissed.
- (void) dismissAlert {
// Do your stuff here an then remove it from the array.
[[self allMyAlerts] removeObject:self];
}
You may want to add some stuff to make it mutli threading save, which it is not. I just want to give an example that explains the concept.
allMyAlert could be an NSMutableSet as well. No need for an array as far as I can see. Adding the object to an array or set will add 1 to the retain count and removing it will reduce it by 1.
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
I have been struggling with this problem for a while now, so any help would be greatly appreciated.
Here is the situation: My application has a UIViewController subclass called InitialViewController. This view controller has a UIButton, and when that button is pressed it creates a NSObject subclass called MyEngine. Something like this:
#interface InitialViewController : UIViewController <MyEngineDelegate>
...
#end
#implementation InitialViewController
...
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine start];
}
Inside start, I present a ViewController (ConflictViewController) modally to get the user's choice:
#interface MyEngine : NSObject <ConflictViewControllerDelegate>
...
-(void) start;
#end
#implementation MyEngine
...
-(void) start {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = self;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
#end
ConflictViewController is really simple. It just waits for the user to decide, and when the user press the button, it send the message to the delegate, and dismiss itself.
-(IBAction)didSelectConflict:(id)sender {
UISegmentedControl *seg = (UISegmentedControl*) sender;
[self.delegate didResolveConflictChoice:seg.selectedSegmentIndex];
[self dismissModalViewControllerAnimated:YES];
}
I've checked every connection, all the delegates are working properly.
What is going wrong is:
When MyEngine receives the user's choice in it's implementation of didSelectConflict: it cannot continue properly because all of it's properties have gone null.
When the MyEngine presents the ConflictViewController, the program continues the execution and when start finishes, it goes back to pressedButton: and when this method is closed, the MyEngine object gets released.
What i want to know is if there is way around this ? Has anyone done something like this in another way ?
The question here is: How to get the user's choice properly when the choice is too complex to use UIAlertView.
Sorry for the long question, I simplified it as much as I could. Thanks for your time, any links, comments, or any kind of help is greatly appreciated
Why are you initializing MyEngine *engine in the IBAction, if you wish to use a MyEngine object why don't you make a global declaration in your InitialViewController and just call [engine start] in the IBaction. Then when the delegate method returns the selected index you can apply that to a global int in your initial view controller and continue on your way. Hope that makes sense
Make your method start as
-(void) startWithDelegate:(id)del {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = del;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
and
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine startWithDelegate:self];
}
implement didResolveConflictChoice: in InitialViewController and get the delegate call there.
OR you can use UIActionSheet if suitable.
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.