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!
Related
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;
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).
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!
I'm not understanding why this is giving me a EXC_BAD_ACCESS. Basic background, im collecting and processing some information, and then using a modal view to let the user confirm if he want to continue.
I have a button on the navigation bar called continue, that calls my data prepare function.
- (void)viewDidLoad {
//Other stuff
UIBarButtonItem *next = [[UIBarButtonItem alloc]
initWithTitle:#"Next"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(prepData)];
self.navigationItem.rightBarButtonItem = next;
[next release];
[super viewDidLoad];
}
prepData:
-(void)prepData{
/*
There's a bunch of stuff going on here, if "mensaje" is not an empty NSString, there is some kind of error that wont let me go on, if not, everything in the data is fine
*/
if(![mensaje isEqualToString:#""]){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:nil
message:mensaje
delegate:nil
cancelButtonTitle:#"Aceptar"
otherButtonTitles:nil];
[alert show];
[alert release];
}else{
UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:#"¿Esta seguro que desea realizar estas operaciones?"
delegate:self
cancelButtonTitle:#"Cancelar"
destructiveButtonTitle:#"Aceptar"
otherButtonTitles:nil];
[actionSheet showInView:self.view];
[actionSheet release];
}
}
If I debug, I can get all the way through the prepData(), as soon as I press continue I get a EXC_BAD_ACCESS. If I comment out the [actionSheet release]; I dont get the exception, but it is to my knowledge, that just like alert views, action sheets "stick around" until they show.
At least all books I've read state that, but its quite possible that I'm not understanding something in the autoreleasing.
Just for reference, the alert pops up just fine.
Anybody have any idea whats going on here?
Thanks, Stefano.
Edit: Figured it out, the above code for actions sheets and alert views is fine, the issue was that I was releasing something that was later trying to be autoreleased.
I have for loop that does this:
for(someConditions){
NSString *montoFormateado = [[[NSString alloc] initWithFormat:#"%.2lf",[monto doubleValue]] stringByReplacingOccurrencesOfString:#"." withString:#","];
[_postBuild setObject:[NSString stringWithString:montoFormateado] forKey:[NSString stringWithString:iidvar]];
[montoFormateado release];
}
postBuild = [_postBuild mutableCopy];
[_postBuild release];
Now, it seems the error was in the fact that by using [NSString strintWithString:montoFormateado] I was leaving that string up for autorelease later, but when I released _postBuild that string was getting released also, I removed that and just used setObject:montoFormateado and its working fine.
Its leaking memory, but I think thats for a diferent question, the exc_bad_access got solved.
The easiest way to find this kind of bug is to use the NSZombieEnabled environment variable.
When you get an EXC_BAD_ACCESS at the end of a call, it usually means that something is being autoreleased, but it's retain count is already 0 from you releasing it.
Check this post, it'll save your life a lot of times. If that doesn't work, leave a comment and we'll put our debugging caps back on :)
I think there's one release too many in the code you didn't show (in the "There's a bunch of stuff going on here, if "mensaje" is not an empty NSString" block).
There's nothing wrong with your actionSheet code. As you say, it should deallocate itself once it's no longer on screen. But if you over-released one of the sub-views of self.view then it could cause a crash when the autorelease pool is drained.
In both cases, if you're gonna "release", you better "retain" first.
In the case of the UIActionSheet - I believe the view passed into showInView is retaining a copy of the actionsheet.
In the AlertView this is not the case.
Maybe you are releasing the UIAlertView before its used, try removing the release statement and declare the UIAlertView as auto release (though it should be fine the way you have it)
if(![mensaje isEqualToString:#""]){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:nil
message:mensaje
delegate:nil
cancelButtonTitle:#"Aceptar"
otherButtonTitles:nil] autorelease];
[alert show];
Another thing that it might be is that you are releasing these guys again on some call back or something, you should check you arent doing that, and like the poster above me said, you can use zombies to figure this stuff out as well.
My alertview appears twice and requires 2 user clicks to dismiss.
- (void) showAlert: (NSString *) message
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"You chose"
message: message
delegate: self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"ok",nil];
av.tag = SLIDER_ALERT;
[av show];
}
I am then releasing the alertview in the delegate method "alertView: (UIAlertView *) alertView clickedButtonAtIndex: (int) index"
I suspect the problem is that I have built my own view hierarchy programmaticaly. I have one parent view for the viewcontroller. Under that i have 2 other views (parentView -> subview1 and subview2). I've tried to call [self.view addSubview: av] but that does not work. Neither does bringToFrontSubView:
Any help would be much appreciated
Peyman
The Alert code is fine (other than the release, mentioned in the comments).
[av show] is all that's required to show a view. You don't add UIAlertViews as subviews.
Call it after a delay of 0.1 sec [self performSelector:#selector(showAlert:) withObject:nil afterDelay:0.10];
Hope this will solve your problem.
With using autorelease the Alert View seems to be twice or 3 times. And for iOS4 it needs to be autoreleased otherwise it will crash.