in my program I have two ways to dismiss an uialertview: one by button click and one by nstimer. If the button is clicked before the timer fires my program quits when the timer fires. How do I check the status of the uialertview so that the timer knows whether or not to execute a dismiss message? And what NSLog statement should I use for debugging.
My guess is that you're app is crashing with an exception because you got stale pointer to the deallocated UIAlertView. I guess that you're not resetting the instance variable that references the UIAlertView.
You surely got an instance variable to save the UIAlertView pointer. You also need to set the delegate property, like this:
myAlert = [[UIAlertView alloc] initWithTitle:...];
myAlert.delegate = self;
[myAlert show];
// Now setup the timer
Your class must implement the UIAlertViewDelegate protocol, specifically you need to implement alertView:willDismissWithButtonIndex: and/or alertView:didDismissWithButtonIndex: (I'd use the later). In this method, you reset the instance variable:
[myAlert release];
myAlert = nil;
// Also cancel timer
Canceling the timer isn't that important any more as setting the variable to nil is the important part. Once the timer would fire it wouldn't crash any more (but canceling the timer would still be a good idea).
Related
I solved a crash with something that sounds weird do me : calling [self retain] and [self autorelease].
Here's the case :
MyObject is a subclass of a UIView, so usually it's allocated, put on the view stack, and released. MyObject also has a timer, which will remove itself from the superview. So basically MyObject can be deallocated at anytime.
Apart from displaying cool stuf, MyObject is also able to displays a UIAlertView, and waits for the user's choice, so it's the alertView's delegate.
The problem is that if the user makes a choice after MyObject is deallocated... well you know EXC_BAD_ACCESS I guess...
So I could have kept a reference to the AlertViews (yes, there are severals), and set the delegate to nil in the MyObject dealloc method. But I couldn't process the choice then (and as there are several, it would make more variable instances, which I don't like.
So what I did is :
//alertView creation
-(void)showAlert{
[self retain];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"title"
message:#"message"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes",nil];
[alertView show];
[alertView release];
}
//Delegate method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
[self autorelease];
// Do the job
}
I know it's not nice, I've put many comments around this to make other developers that will reuse the code be careful, but I can't see another option. Is it correct ? Is there a better way todo it ?
Keep in mind that the delegate pattern, in order to avoid having a cyclic dependency which prevents the main object as well as the delegate from being released since they both have a reference to each other, requires that the main object does not retain the delegate. So if you create a delegate on the fly and assign it to an object, you have to retain and autorelease it yourself!
Frankly, your program structure is all gnarly, and that's what is causing your trouble. You should move all of the delegate functionality and the responsibility for managing the views into a separate controller object.
First of all, I don't see a problem with retaining self. I also did it in some multi-threaded code. Maybe not the best solution but it worked.
Now, if you want to avoid [self retain], consider the following:
Maybe instead of straight deallocating when the timer fires, you instead check if there's an Alert presented. If not, release. If yes, hide the view (but don't remove from superview) and set a flag in a new ivar that the object should be removed from superview. In the sheet's callback, check the flag and if set, remove the view from the superview, releasing it.
Can't you make your alertView an #property or instance variable and just set alertView.delegste = nil in MyObject's dealloc?
I have an in app purchase in my app and when the purchase is "purchasing", or in progress, I have an alert view come up that says "Loading...". When the purchase was successful, restored, or failed, I'd like to call a method that releases the alert view. The only problem is, I can't try to release the alert view in another method because it will have no idea what alert view I am talking about and will produce an error. Now I have no idea if this is even the best way of trying to accomplish this, so all ideas are appreciated. Thanks!
case stateIsPurchasing { //or whatever it's called
UIAlertView *alert = [[UIAlertView alloc] message and delegate and button stuff here];
[alert show];
[alert release];
}
The UIAlertView is definitely not the right control to do this.
You should use the UIProgressView if you are able to display finite progression or UIActivityIndicatorView to show the "spinner".
I wouldn't use an alert view for this. Look for a progress HUD, such as SVProgressHUD, an excellent and beautiful loading view.
http://cocoacontrols.com/platforms/ios/controls/svprogresshud
SVProgressHUD functions as a singleton, so you can show/stop it from any class.
Declare a UIAlertView in your header as a retained property, synthesized, and released in dealloc. Store the alert view that you create in that code snippet using this pointer, and use the declared pointer in your other method. But don't call [alert release]; when you create the alert view (unless you like EXC_BAD_ACCESS errors!). Oh, if you're adding in-app purchases, watch out for Lodsys...
I'm calling a method I've written in app delegate which calls an alertView to ask the user for a password, I use the method in applicationWillEnterForeground also.
So the value is set in clickedButtonAtIndex in app delegate.
How do I determine when the value has been set in the view controller (where I'm calling the method) ?
The simplest way to do this would be to delegate back to your viewController.
Your app delegate will have the ability to access the pointers to any ViewControllers/navControllers that you have. (scope depends on your design of course)
Here is a good Post on objective-c delegates.
I found myself doing this kind of thing quite often to simplify the process i had a method contained in a singleton that i called 'toolbox' that went something like this.
-(void)showAlertWithTitle:(NSString*)_title Message:(NSString*)_message CancelButtonTitle:(NSString*)_cancelTitle AlternitiveButtonTitle:(NSString*)alternitiveButtonTitle AndDelegate:(id)del
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:_title message:_message delegate:del cancelButtonTitle:_cancelTitle otherButtonTitles:alternitiveButtonTitle, nil];
[alert show];
[alert release];
}
This meant that i could call an alert where i wanted and have the Alert delegate back to where i wanted, then just implement UIAlertViewDelegate there.
I have a UIAlert that pops up 3 times every time it is called. It appears and then disappears before I can click on it. Could it be that the viewDidLoad itself is being called 3 times?
I implemented an UIAlert in the viewDidLoad of my app:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:alertMessage delegate:self cancelButtonTitle:ok otherButtonTitles:nil];
This is the viewDidLoad in the rootViewController, that manages the views:
- (void)viewDidLoad {
Kundenkarte *kartenAnsicht = [[Kundenkarte alloc]
initWithNibName:#"Kundenkarte" bundle:nil];
kartenAnsicht.rootViewController = self;
kartenAnsicht.viewDidLoad;
self.map = kartenAnsicht;
[self.view addSubview:kartenAnsicht.view];
[kartenAnsicht release];
// [super viewDidLoad];
}
The viewDidLoad that evokes the UIAlert is in the kartenAnsicht view controller.
I hope someone can help me because I'm out of ideas.
You don't need to call -viewDidLoad yourself, it's run automatically by the NIB-loading mechanism. That means you get extra invocations of -viewDidLoad: one by design, and extras whenever you call it.
First of all, you should never put any type of display in viewDidLoad. That method is intended for behind the scenes configuration after the view is first read from nib. There is no certainty that it will be called every time the view displays because after the first time it loads, the view maybe held in memory and not reloaded from nib.
Instead, put the call to evoke the NSAlert in viewWillDisplay or viewDidDisplay. This will display the alert each time the view appears.
I doubt that viewDidLoad is called three times but to check for that just put an NSLog in the method to see how many times it is called.
When you say that:
i implemented an NSAlert in the
viewDidLoad() of my app:
... what does that mean? What object exactly has the method? If it is the application delegate, this will not work because the application delegate protocol does not respond to viewDidLoad. It has to be in a UIViewController.
Edit01:
See this post that had the same problem: UIAlertView Pops Up Three Times per Call Instead of Just Once
Short answer: You kill the alert by releasing it. Either retain it as a property of the view controller or better yet, display the alert with runModal instead of show and capture the button number returned immediately.
It would be helpful to see the code around the alert call.
I am using an alert whenever the reachability changes. Since reachability is checked repeatedly, the alert could get called repeatedly. To alleviate that, I wrap the alert code like so:
if (!myAlert) { /* set up and show myAlert */ }
However, one problem with this is that when you click the Cancel button, the alert will remain non-nil, and therefore can never show again because of that conditional. If someone could add to this response with a fix for that, that would be great. I assume I can add a handler for the cancel button that will destroy myAlert.
If learned the hard way that you should remove the delegate from an object if the life span of the delegate is shorter than the object. But how do you do this if you don't have a reference to the object anymore?
In my iPhone application I have a view controller vc which performs an asynchronous activity and is displayed as a modal view. A cancel button dismisses the modal view. If an error occurs, an UIAlertView alert is displayed. If the user taps on ok, both alert and the modal view disappear. Therefore vc is set
as delegate for alert and implements alertView:didDismissWithButtonIndex:. Something like this:
// UIViewController vc
...
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Something went wrong"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
self.alertView = alert; // needed to unset alertView.delegate in dealloc
[alert show];
[alert release];
...
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[self dismissModalViewControllerAnimated:YES];
}
}
Normally, the alert view blocks all input. Unfortunately, it fails to do so in some edge cases.
If the user touches down on the cancel-button before the alert view appears and touches up after the alert view appears, the view is dismissed, but not the alert. vc gets deallocated and if the user then taps "ok" on the alert, the application crashes because a message was sent to the released object.
I solved this by assigning alert to a property of vc so I can set alert.delegate to nil in dealloc. I find this solution to be not very elegant, because I don't really need the reference to alert.
Is there a better approach?
Edit: Added the text in italics as clarification
Although, normally an alert view is presented over non changing content. So if the delegate is alive when the view appears it will likely be alive when it's dismissed. If that's not the case, you have to do exactly what you did, and unset the alert view's delegate manually if you no longer care about it's result.
So you do care about the alertview since you care about it's delegate method. The wrinkle is that the delegate may not apply by the time the alert is dismissed. So you need logic there, and for that logic you need to save a reference to the alert view in question.
In other words, you are doing it right. Although, it may have been helpful if UIAlertView retained it's delegate, but it doesn't seem like it does if it crashes when it's dismissed.
Lastly, I thought alert view blocked all screen input? If not, you can make it truly modal by setting vc.view.userInteractionEnabled = NO when the alert appears and switch it back when it's dismissed. This way the user can't dismiss the controller while the alert view is up. Which sounds a bit more sane to me.
From a UI perspective, when an alert view is present, it should demand the user's full attention. Perhaps you might consider disabling the Cancel button (as well as any other visible non-alert-view widgets) when there is an alert view present, and adding a button title to the UIAlertView instance which performs the same task. This would provide a clearer user interface and should also neatly solve your memory issue.
-(void)delayedDismiss {
[self dismissModalViewControllerAnimated:YES];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[self performSelector:#selector(delayedDismiss) withObject:nil afterDelay:0.0];
}
If you no longer care about the results of the UIAlertView you should probably also dismiss it in the - (void) viewWillDisappear:(BOOL)animated