Dismiss UIAlertView when task is finished - iphone

In my app I have an advanced search page which uses a tableview with 4 cells to access 4 tableviews as drop-down fields basically. These are populated from the results of parsing a xml I receive from a url request that happens when the app first loads and is stored in the appDelegate to be accessed by the tableviews later when the advanced search page is accessed. In normal, good internet connection circumstances, this takes a split-second, so the information is there far before the adv. search page could even be accessed, but I want to cover myself in case of a really slow connection.
So, I am putting an alertview with a spinner over the advanced search page if a flag I set is false(the data is still being requested/parsed). I was able to get this to work, and I had the cancel button taking the user back to the previous page in case they didn't want to wait. But I also need the alert to be dismissed if the flag changes to true(meaning the parsing is done), and that is where I'm starting to have trouble. I'm starting an infinite loop to keep checking if the flag is true, and then dismissing the alert when it breaks out of it. I put it in a separate thread so the loop doesn't block the user from being able to hit the cancel button, but I'm getting all sorts of runtime errors about:
-[UIAlertView dismissWithClickedButtonIndex:animated:]: message sent
to deallocated instance 0x105330
Here's the relevant code:
-(void)viewDidAppear:(BOOL)animated
{
[NSThread detachNewThreadSelector:#selector(waitTilDone) toTarget:self withObject:nil];
}
-(void)waitTilDone{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (mainDelegate.artistDone == #"no" || mainDelegate.mediumDone == #"no" || mainDelegate.seriesDone == #"no" || mainDelegate.areaDone == #"no") {
}
[av dismissWithClickedButtonIndex:0 animated:YES];
[pool release];
}
-(void)viewDidLoad
{
mainDelegate = (PublicArtOmahaAppDelegate*)[[UIApplication sharedApplication]delegate];
if (mainDelegate.artistDone == #"no" || mainDelegate.mediumDone == #"no" || mainDelegate.seriesDone == #"no" || mainDelegate.areaDone == #"no") {
av=[[UIAlertView alloc] initWithTitle:#"Loading Form Data" message:#"\n" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:nil];
UIActivityIndicatorView *ActInd=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[ActInd startAnimating];
[ActInd setFrame:CGRectMake(125, 40, 37, 37)];
[av addSubview:ActInd];
[av show];
[av release];
}
[super viewDidLoad];
}
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked on the Cancel button
[self dismissModalViewControllerAnimated:YES];
}
So my questions are:
Is the infinite loop a crappy way of checking if I can dismiss it? I need to do it somehow... :/
Obviously I'm managing memory poorly here, but I'm pretty new to this so I don't know what I'm doing wrong. Is there a quick fix there?

For 1): Yes, that is crazy. Use key/value observing (KVO), [NSNotificationCenter defaultCenter], write your own delegate, or something else, to be notified when the value actually changes, instead of polling it.
For 2): Don't release it until you're done with it. Properties can be helpful here, since it simplifies a lot of things.
// In header
#property (nonatomic, retain) UIAlertView *av;
// When showing it
self.av = [[[UIAlertView alloc] initWith....] autorelease];
...
// When dismissing it
[self.av dismiss];
self.av = nil;

Related

Get immediate value from UIALertView

I have what can be most accurately described as a Factory, which is generating some NSOperations. Before the NSOPerations are generated, I would like to check the current network status and, if the user is on a 3G/Mobile connection, warn them that they are about to do a data-heavy operation.
I attempted to do this with a UIAlertView, but the only way I can see to get the "response" from a UIAlertView is via the event-based delegate system. I was wondering if there was any way to have it act like the "confirm" dialogue in JavaScript, where it blocks the UI and I can get an immediate value from it once it is dismissed.
Is there any standard way to do this, or some example code I could be pointed towards that accomplishes something similar?
Blocking the main thread is considered bad practice on iOS, and thus there is no synchronous API for UIAlertView.
You should implement a delegate callback for the alert that enqueues the relevant NSOperation. It may be useful to subclass UIAlertView to store the relevant data you need to enqueue the NSOperation, or better yet store a block that captures the relevant variables and then just execute that when the user confirms the dialog.
You can implement something similar to that, using blocks. The execution will continue as in all other cases, but the flow of reading your code might more resemble what you want. Here is a helper class that I made for that purpose so that I can just go:
[YUYesNoListener yesNoWithTitle:#"My Title" message:#"My Message" yesBlock:^
{
NSLog(#"YES PRESSED!");
}
noBlock:^
{
NSLog(#"NO PRESSED!");
}];
...and here is the helper class:
typedef void(^EmptyBlockType)();
#interface YUYesNoListener : NSObject <UIAlertViewDelegate>
#property (nonatomic, retain) EmptyBlockType yesBlock;
#property (nonatomic, retain) EmptyBlockType noBlock;
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock;
#end
#implementation YUYesNoListener
#synthesize yesBlock = _yesBlock;
#synthesize noBlock = _noBlock;
- (id) initWithYesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
self = [super init];
if (self)
{
self.yesBlock = [[yesBlock copy] autorelease];
self.noBlock = [[noBlock copy] autorelease];
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0 && self.noBlock)
self.noBlock();
else if (buttonIndex == 1 && self.yesBlock)
self.yesBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
- (void) alertViewCancel:(UIAlertView *)alertView
{
if (self.noBlock)
self.noBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
YUYesNoListener* yesNoListener = [[YUYesNoListener alloc] initWithYesBlock:yesBlock noBlock:noBlock];
[[[UIAlertView alloc] initWithTitle:title message:message delegate:yesNoListener cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil] show];
}
#end
Using the code of Ricky Helgesson, I've built a Pod component to use this solution easily in any project that uses CocoaPods.
https://github.com/nmaletm/STAlertView
The code that you should use is:
[[STAlertView alloc] initWithTitle:#"Title of the alert"
message:#"Message you want to show"
cancelButtonTitle:#"No" otherButtonTitles:#"Yes"
cancelButtonBlock:^{
// Code todo when the user cancel
...
} otherButtonBlock:^{
// Code todo when the user accept
...
}];
And add at the Podfile:
pod "STAlertView"
There are more instructions at the github page.

issue dismissing UIAlertView

I'm using UIAlertView with "please wait" and UIActivityIndicatorView. I show it using
{
...
[self performSelectorInBackground:#selector(sh) withObject:nil];
//i've tried also
//[NSThread detachNewThreadSelector:#selector(showWaiting) toTarget:self withObject:nil];
...
}
- (void)showWaiting {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[waitingAlert show];
[pool drain];
}
When alert appears, screen becomes darkened and inactive. And when time to dismiss alert comes I do
[waitingAlert dismissWithClickedButtonIndex:0 animated:NO];
After this alert disappears, but sometimes (chance is about 5%) screen remains as before darkened and inactive.
Has someone faced such problem?
Thanks!
This isn't the answer you're looking for... but it's the "correct" answer and will save you some time in the long run:
You're misusing UIAlertView. It shouldn't be used for progress indicators and "Please Wait" dialogs. It's designed to be dismissed by the user hitting a button -- a UIAlertView should never, ever just disappear on its own (correction: actually, it can; Apple's API supports dismissing of it. But such a thing should be used very carefully.)
If you continue to abuse UIAlertView that way, don't be surprised when your app is rejected during app store submission and you then have to redo it another way.
Please see also my answer to this question:
Multiple AlertViews - Remove the alertview that is behind another alertview
As occulus states avoid using UIAlertView like this,
I recommend MBProgressHUD, it's very easy to use, looks great and is designed for this purpose.
You have to create, show and release the alert outside the NSAutoreleasePool. For example, you make your call in background:
...
[NSThread detachNewThreadSelector:#selector(showWaiting) toTarget:self withObject:nil];
...
And the call has an autorelease pool:
- (void)showWaiting {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorInMainThread:#selector(showMyAlert) withObject:nil waitUntilDone:NO];
//[waitingAlert show];
[pool drain];
}
Then you must to show the alert using the main thread.
- (void)showMyAlert {
UIAlertView * myAlert = [[UIAlertView alloc] initWith...];
[myAlert show];
[myAlert release];
}
If you try to show the alert inside the autorelease pool and this is drained then the alert dissapears.
I hope this helps!

Update text in alert message

Still trying to update the message in an UIAlertview while the alert is active. I removed most of the first part of the question and publish more code in the update below.
UPDATE 3: Adding more code!
In the .h file I declare the following (among others):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
UIAlertView *alert;
}
I #property and #synthesize the UIAlertview to.
Next I create the alert in an IBAction which is run by a button click:
-(IBAction)convert {
convertButton.enabled = NO;
mailButton.enabled = NO;
backButton.enabled = NO;
//show the alert window
alert = [[UIAlertView alloc] initWithTitle:#"Converting in progress\nPlease Wait..." message:#"\n\n\n" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[indicator startAnimating];
[alert addSubview:indicator];
[indicator release];
[alert setMessage:#"getting roster"];
}
It then jumps to the following function:
- (void)didPresentAlertView:(UIAlertView *)progressAlert {
//A lot of code
[alert setMessage:#"Checking history"];
//more code
[alert setMessage:#"writing history"];
//even more code
[alert setMessage:#"converting roster"];
}
The didPresentAlertView method ends with an ASIHTTPRequest to post data to a webpage and when this request is finished the code finally jumps to the last method to exit the UIAlertView and closes everything up:
- (void)requestFinished:(ASIHTTPRequest *)request {
[timer invalidate];
[alert dismissWithClickedButtonIndex:0 animated:YES];
backButton.enabled = YES;
[alert release];
}
I also removed the autorelease from my UIAlertView init to make sure it exists during the rest of the process.
As it is now, the code only fires the very first setMessage -> 'getting roster' and the very last -> 'converting roster'. The setMessage requests in the middle do not get fired..
Really hope someone can help me here!
Thanks all!
Now I see the problem.
When you update the message property, it doesn't fire redrawing the view right away. The view is marked as 'needed to be drawn', and the actual drawing happens later, typically at the end of the current or next runloop in the main thread.
Therefore, while your didPresentAlertView method is running on the main thread, the alert view is not redrawn until the method is finished. This is why a computation-intensive job needs to run on a separate thread to increase the responsiveness of the UI, as the UI-related job is done on the main thread.
What you should do is run your //A lot of code //more code and //even more code on a separate thread, and update the message property on the main thread.
For example, your code may look similar to :
// this is inside didPresentAlertView method
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock: ^{
// A lot of code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"Checking history" waitUntilDone:NO];
// more code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"writing history" waitUntilDone:NO];
// it goes on
}];
If you are working with iOS 4.0 and later, and want to use the GDC, be careful because it may detect independency of your computation and message updates and let them happen concurrently (which is not desired here).

UIAlertView choice causing resignFirstResponder to fail

I'm having a similar issue to Anthony Chan's question, and after trying every suggested solution, I'm still stuck.
Somehow, only after interacting with my UIAlertView, I'm unable to dismiss the keyboard in another view of my app. It's as though the Alert is breaking my UITextField's ability to resignFirstResponder. Below I instantiate my UIAlertView, which then calls its didDismissWIthButtonIndex method. Then, I call the showInfo method, which loads another UIViewController.
UIAlertView *emailFailAlert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"error message text."
delegate:self
cancelButtonTitle:#"Not now"
otherButtonTitles:#"Settings", nil];
[emailFailAlert setTag:2];
[emailFailAlert show];
[emailFailAlert release];
Once the 'Settings' option is pressed, I'm calling this method:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if ([alertView tag] == 2) {
if (buttonIndex == 1){
[self showInfo:nil];
}
}
}
My showInfo method loads the other ViewController, via the code below:
- (IBAction)showInfo:(id)sender {
FlipsideViewController *fscontroller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
fscontroller.delegate = self;
fscontroller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:fscontroller animated:YES];
[fscontroller release];
}
Upon clicking any textField in this Flipside VC, I'm unable to dismiss the keyboard as I normally can with - (BOOL)textFieldShouldReturn:(UITextField *)textField, and [textField resignFirstResponder]. I've omitted this code bc this question is getting long, but I'm happy to post if necessary.
The interesting part is that if I comment out the [self showInfo:nil] call made when the button is clicked and call it by clicking a test button (outside the alertView didDismissWithButtonIndex: method), everything works fine. Any idea what's happening here?
Thanks in advance!
When an alert, with more than one dismissal option, is called above a keyboard - the keyboard becomes un-dismissible with resignFirstResponder on the active textfield;
You will need to dismiss the keyboard before showing the alert.
Assuming your UITextField is called myTextField;
[myTextField resignFirstResponder]; //That's the only line I added
UIAlertView *emailFailAlert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"error message text."
delegate:self
cancelButtonTitle:#"Not now"
otherButtonTitles:#"Settings", nil];
[emailFailAlert setTag:2];
[emailFailAlert show];
[emailFailAlert release];
I hope this helps anyone who had to deal with this oddly obscure issue.
You should not call alertView:didDismissWithButtonIndex: directly. This delegate method will be executed automatically in all cases after the alert has disappeared. Otherwise the code will be run twice!

Click on UIAlertView crashes app if view is dismissed

A UIAlertView is displayed if an error occurs. But in the meantime the view on which the UIAlertView were called has been dismissed (and therefore released). If the user clicks on OK the app crashes because a message to a released instance is sent. This will cause your app crashing:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
alertView = nil;
[self.navigationController popViewControllerAnimated:YES];
I thought the UIAlertView is an independent unit. But it seems it isn't. Is there a way how I could avoid the app crashing (except not dismissing the view)?
The delegate is called when the UIAlertView is dismissed, so in your case:
delegate:self
Delegates are not retained, like an object added to an array, or a subview would be. So in your case, when you call:
[self.navigationController popViewControllerAnimated:YES];
self is most likely being released, and when the the user dismisses the alert, self is called, but has been dealloc'd so it no longer exists.
An easy way to check this is to put a logger statement, like NSLog(#"I'm gone"); in self's dealloc method, if it's ran, then you know your self isn't around anymore, and any messages sent to it will cause a crash.
Make the UIAlertView a retained property of your view controller so that you can refer to it in your dealloc, then set the alert view's delegate to nil when the view controller is deallocated.
Be sure to properly release the retained alert view once it's been dismissed and on dealloc.
For instance:
#interface MyViewController : UIViewController <UIAlertViewDelegate> {
UIAlertView *alertView;
}
#property (nonatomic, retain) UIAlertView *alertView;
#implementation MyViewController
#synthesize alertView;
- (void)showAlert {
if (alertView) {
// if for some reason the code can trigger more alertviews before
// the user has dismissed prior alerts, make sure we only let one of
// them actually keep us as a delegate just to keep things simple
// and manageable. if you really need to be able to issue multiple
// alert views and be able to handle their delegate callbacks,
// you'll have to keep them in an array!
[alertView setDelegate:nil];
self.alertView = nil;
}
self.alertView = [[[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Something went wrong"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Retry",nil] autorelease];
[alertView show];
}
- (void)alertView:(UIAlertView *)alertViewParam didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.alertView = nil; // release it
// do something...
}
- (void)dealloc {
[alertView setDelegate:nil]; // this prevents the crash in the event that the alertview is still showing.
self.alertView = nil; // release it
[super dealloc];
}
The downside here is that you will not be able to handle the alert view's callback when the user dismisses it. However, since your controller is already gone/released, presumably you don't need to. If you do, you have to set the alert view's delegate to something that will persist.
If the UIAlertView object is to be usable from anywhere in the app, not just on the current view, then retain it inside something that is available from anywhere in the app, either some persistant root view controller under the entire possible view stack, or the app delegate.
Added:
This top level object can also retain the alert view's delegate until after it's done being needed (after alert view dismissal).
(People might wonder I am late by years in answering this question,but it might help someone else)
I guess your problem lies some where in popping the view controller,you are displaying the alert view and at the same time trying to navigate the user back to a view.I would recommend you to follow a hierarchal approach here i.e.:
First of all declare your alert view as a global object,i.e.
#property(nonatomic,retain) UIAlertView *sampleAlert;
Now write your alert view display code where ever desired,say for instance:
-(IBAction)buttonClicked:(id)sender
{
self.sampleAlert = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[sampleAlert show];
[sampleAlert release];
}
Finally try to navigate the user to the desired view when the "Ok" button is pressed,i.e. you need to make use of alertView didDismissWithButtonIndex method,i.e.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if(alertView == sampleAlert)
{
[self.navigationController popViewControllerAnimated:YES];
}
}
Please note that if you have alert view with multiple buttons,you also need to check for button index for distinguishing actions,i.e. check using
if(alertView == sampleAlert && buttonIndex == 0)
{
//Do your stuff
}
else
{
//Do something else
}
This will definitely avoid application crash,thanks :)
Easier way that worked for me is to hold all the alert views in a Array and when the parent view is deallocated enumerate alertViews array and assign the delegate to nil. This will ensure that the touch event is ignored and app will function.
// ARC world
#property (strong, nonatomic) NSMutableArray *alertViews;
- (void)dealloc
{
[self.alertViews makeObjectsPerformSelector:#selector(setDelegate:) withObject:nil];
}
Make sure you are implementing the UIAlertViewDelegate protocol.
If you don't care about when the alert is dismissed just init with delegate as nil.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];