views:

290

answers:

5

Apple recommends dismissing any UIAlertViews/UIActionSheets when entering background state in iOS 4. This is to avoid any confusion on the user's part when he relaunches the application later. I wonder how I could elegantly dismiss all UIAlertViews at once, without retaining a reference to it everytime I set one up...

Any idea ?

+1  A: 

I have this on my TODO list, but my first instinct would be to listen out for the notifcation UIApplicationWillResignActiveNotification (see UIApplication) in the views where you have things like UIAlertView - here you can programmatically remove the alert view with:

(void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated

The discussion for this method even suggests what it's for in iOS4!

In iPhone OS 4.0, you may want to call this method whenever your application moves to the background. An alert view is not dismissed automatically when an application moves to the background. This behavior differs from previous versions of the operating system, where they were canceled automatically when the application was terminated. Dismissing the alert view gives your application a chance to save changes or abort the operation and perform any necessary cleanup in case your application is terminated later.

petert
But Francois asked for a solution without retaining the UIAlertView, if you don't retain (i.e retain count = 0), you can never call dismiss.I didn't try but what the behaviour is when you don't call dismiss? Will it appear again when you move back your app to foreground?
vodkhang
Good point, I also follow `[alertView show]` by `[alertView release]`. Docs for `UIAlertView` say "Now, it is up to you to decide whether to dismiss the alert view (and execute its cancellation handler) or leave it visible for when your application moves back to the foreground. Remember that your application can still be terminated while in the background, so some type of action may be necessary in either case.". So i guess you need to decide to keep a reference around or not.
petert
+2  A: 

huh. Haven't tried this yet, but I wonder if it would make sense to create a subclass of UIAlertView that listens for this Notification and closes itself if so...

That'd have the "automatically" without retaining / keeping it around characteristic OP is requesting. Make sure to unregister for the notification on close (else boom!)

Dad
"Bravo!" to Guillaume for taking the time to write the test code I didn't have time to write. http://stackoverflow.com/questions/3105974/dismissing-uialertviews-when-entering-background-state/3181223#3181223 Glad it worked as I'd imagined it would. Seems like a pretty clean and complete answer to the problem to me. Elegant even (?), if I do say so myself :-?
Dad
This is an elegant solution. It shows an appreciation for the strengths of Objective-C.
TechZen
+1  A: 

The straightforward way is to hold a reference to the UIAlertView so you can dismiss it. Of course as petert mentioned you can do it with a Notification or use the delegate method on UIApplication

applicationWillResignActive:

does not always mean that you are going to the background. You will for example also receive that delegate call and notification (you get both) when the user gets a phone call or receives and SMS. So you have to decide what should happen if the user gets an SMS and presses cancel to stay in your app. You maybe want to make sure that your UIAlertView is still there.

So I would dismiss the UIAlertView and save the state in the delegate call when you really go into the background:

applicationDidEnterBackground:

Have a look at Session 105 - Adopting Multitasking on iOS4 of WWDC10 available for free at developer.apple.com. It gets interesting at 16:00 min

Check out this graphic to understand the different states of an application

GorillaPatch
+1  A: 

My call would be to add a category to UIAlertview adding the following function :

- (void) hide { [self dismissWithClickedButtonIndex:0 animated:YES];}

And to suscribe to UIApplicationWillResignActiveNotification :

[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];

Charter
Thanks this gives me a good idea on how to achieve what I want. I'll probably add two methods in a category - (void) showAndHidAutomatically, which will register itself and call show, upon the notification will call another method called hideAndUnregister, which will do just that. Enjoy your bounty reward :-)
François P.
+5  A: 

I was intrigued by Dad's answer (funny username :), and curious why it was down-voted.

So I tried it.

Here is the .m part of a subclass of UIAlertView.

#import "UIAlertViewAutoDismiss.h"


@implementation UIAlertViewAutoDismiss


- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id /**/)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...  {

    if ((self = [super initWithTitle:title message:message delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil])) {

        va_list args;
        va_start(args, otherButtonTitles);
        for (NSString *anOtherButtonTitle = otherButtonTitles; anOtherButtonTitle != nil; anOtherButtonTitle = va_arg(args, NSString*)) {
            [self addButtonWithTitle:anOtherButtonTitle];
        }


        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    }
    return self;
}


- (void) dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

- (void) applicationDidEnterBackground:(id) sender {
    // We should not be here when entering back to foreground state
    [self dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:NO];
}


@end

It works nicely. It's great, because you can just start using it the same way that you used to use UIAlertView.

I haven't had time to test it thoroughly, but I didn't notice any side effect.

Guillaume
The only change I would make is to register the observer in the show method instead of init.
Gotosleep
@Gotosleep good point, it would avoid to call - dismissWithClickedButtonIndex: animated: on an not visible alert. Don't think it's a problem at the moment, but will be better for future compatibility. In this case, make sure that removing an observer not registered is ok, or better, adapt the code to avoid it.
Guillaume
The Problem with this answer is that you will receive the applicationWillResignActive notification also when you are just receiving an SMS which you dismiss. Then you are back in your application and NOT in the background, but you dismissed your alert already. I do not think that this is properly handled. Take a look at the graphik I posted as a link in my answer below.
GorillaPatch
Btw, this is a nice example for using the new block syntax instead of the badly named applicationWillResignActive method. Why do you define if with the sender and do not use it? I think this code is not well designed, both in how it is implemented and also conceptionally.
GorillaPatch
@GorillaPatch You're correct. I edited the answer to register to the proper notification. Nice workflow graphic!
Guillaume
@Guillaume I think it is Apple's fault. They claim you used compile against iOS4 and everything works out of the box. But it does not. One has to really think how to manage the state of the application as there are new entry points now.
GorillaPatch
@GorillaPatch Regarding your second comment, I do not understand your question "Why do you define if with the sender and do not use it?". What do you mean?Regarding the code design and implementation quality: I spend time on SO to learn and improve myself. Always appreciate to get feedback. What do you think is wrong in this code? Seems good to me. What would you change?
Guillaume
@Guillaume My question was if a function has an argument, such as your - (void) applicationDidEnterBackground:(id) sender, the you usually do something with the argument in this case "sender" in the function, which you do not. I was just wondering why it is there. BTW if you like that graphic, feel free to upvote the answer ...
GorillaPatch
@Guillaume Regarding the block syntax: your code could look like this:[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidEnterBackgroundNotification object:self queue:nil usingBlock:^{[self dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:NO];}];Notice that I am not using a string like you do, but instead a static string defined by UIKit. This way you avoid typos and typing as it is auto-completed by Xcode.
GorillaPatch
In case anyone want to use code like this, I have created a repository on github that implements it and the equivalent for UIActionSheet: http://github.com/sdarlington/UIViewAutoDismiss
Stephen Darlington