I try to reload a tableView from appDelegate
in AppDelegate.m, in the method who is in charge for Push Notification, "didReceiveRemoteNotification" i call a UIAlertView everytime when a notification arrives.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Notification received" message:[NSString stringWithFormat:#"%#", titleMsg] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
alertView.delegate = self;
[alertView show];
and when the user click "OK" button, should be a reading of database and reload the tableview
// Reload the table when the user click "OK" button in the alert
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if ([alertView.title isEqualToString:#"Notification received"])
{
if (buttonIndex == [alertView cancelButtonIndex])
{
// Stop the sound for notifications
[self stopSoundForNotifications];
// Refresh table messages
AccueilViewController * avc = [[AccueilViewController alloc] init];
[avc readMsgsFromDB];
[avc reloadTableMsgsReceived];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReadMessagesFromDB" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadDataFromDelegate" object:nil];
}
}
}
In the header i added to the protocol UIAlertViewDelegate
Do you have some ideas? Thanks
You put the condition on cancel button instead of that put the condition on OK button
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if ([alertView.title isEqualToString:#"Notification received"])
{
if (buttonIndex == [alertView OKButtonIndex])
{
// then fire notification
// Stop the sound for notifications
[self stopSoundForNotifications];
// Refresh table messages
AccueilViewController * avc = [[AccueilViewController alloc] init];
[avc readMsgsFromDB];
[avc reloadTableMsgsReceived];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReadMessagesFromDB" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadDataFromDelegate" object:nil];
}
}
It looks like you are creating a new instance of AccueilViewController, rather than accessing an existing instance. So those readMsgsFromDB and reloadTableMsgsReceived are being processed by an instance which is not visible.
If you amend your AccueilViewController code, you can register to receive those notifications, and your existing instance can then act on them to reload the data. In fact, I would use only one notification, and combine the two method calls into one.
So delete the second postNotification, and in your AccueilViewController add the following to your viewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationReceived) name:#"ReadMessagesFromDB" sender:nil];
Then add a method to combine the two actions:
-(void)notificationReceived {
[self readMsgsFromDB];
[self reloadTableMsgsReceived];
}
So, when your AlertView button is pressed, the notification will be posted. The notificationReceived method in your existing AccueilViewController instance will fire, causing the messages to be read from the database and the table to be reloaded. You should also add a call to remove the view controller from the notification centre before it is deallocated:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ReadMessagesFromDB" object:nil];
Related
my problem is that i have a textfield on my viewcontroller which should be get filled by user and to provide options to fill the textfiled there is a arrow button front of textfield. Clicking on it open a new viewcontroller with a list of options. So when the user click on any option from that viewcontroller the data will automatically fill in that textfield and user return on previous viewcontroller. And i can not use preprefor segue in it so please provide an answer. If i am missing something let me know i ll comment or edit.
You can use NSNotificationCenter to fill back your UITextField. Add Observer to your first viewController class where you want to fill the textField. Add this lines of code in viewDidLoad-
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fillMyTextField:) name:#"fillMyTextField" object:nil];
than add selector method in same class--
- (void)fillMyTextField:(NSNotification *)notification
{
self.myTxtField.text = (NSString *)notification.userInfo;
}
Now in your other viewController class where you select data for textField. Write the below code in method where you select your data like-
- (IBAction)selectDataAndBackToPreviosVC:(UIButton*)selectedOptionBtn {
id object=[NSString stringWithFormat:#"%#",selectedOptionBtn.titleLabel.text];
[[NSNotificationCenter defaultCenter] postNotificationName:#"fillTextField" object:nil userInfo:object];
[self.navigationController popViewControllerAnimated:YES];
}
in this I have used navigationViewController which use Push and Pop viewControllers and i have some UIButton in option selection viewController. I'm passing button title which is options to myTextField on IBAction.
In class where textfield Define Below method (add notification observer)
in viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fillTextField:)
name:#"fillTextField"
object:nil];
- (IBAction)fillTextField:(NSNotification *)sender
{
textField.text = (NSString *)notification.object
}
In Another class where u select date set below method in date selection action
[[NSNotificationCenter defaultCenter] postNotificationName:#"fillTextField" object:nil userInfo:textFieldInfo];
You can use NSNotification:
In View controller ofTextField add this
- (void) viewDidLoad
{
//Your rest of code then below statement
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveTextNotification:)
name:#"TestNotification"
object:nil];
}
- (void)receiveTextNotification:(NSNotification *)notification {
NSLog(#"%# updated", [notification userInfo]);
}
- (void) dealloc
{
// If you don't remove yourself as an observer, the Notification Center
// will continue to try and send notification objects to the deallocated
// object.
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
In View controller of Button, on button click add this:
- (IBAction)fillTextFieldAction: (id) sender {
// All instances observing the `TestNotification` will be notified
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:self userInfo:text]; //Object here can be any changed value .
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fillTextFieldAction:) name:#"fillTextField" object:nil];
- (IBAction)fillTextFieldAction:(NSNotification *)notification
{
textField.text = (NSString *)notification.object
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"fillTextField" object:nil userInfo:textFieldInfo];
I'm using LocalNotifications in my AppDelegate.m
If the user has the app open, the notification comes in the form of an alert.
The AppDelegate.m receives the clickedButtonAtIndex event. Regardless of the current view the user sees, the alert shows and everything works fine so far.
However, when receiving the event, I'd like to change the state of a UISwitch that exists on a UIVIewController.
EDIT: ADDED MORE CODE
My App is set up this way:
AppDelegate.m has this code:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
// Called from the local notification above when the View button is clicked and the app reopens
//called when app is open vs in background
NSLog(#"got notification");
UIApplicationState state=[application applicationState];
if(state==UIApplicationStateActive){
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:#"Notice"
message:notification.alertBody
delegate:self cancelButtonTitle:#"Sleep"
otherButtonTitles:#"Turn Off", nil];
[alert show];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"clicked button");
if(buttonIndex==1){
SettingsPage *setPage = [[SettingsPage alloc] initWithNibName:nil bundle:nil];
[setPage clickedAlert];
}
}
SettingsPage.m has this code:
#interface SettingsPage()
#property (weak, nonatomic) IBOutlet UISwitch *alarmSwitch;
#end
#implementation SettingsPage
-(IBAction)setAlarm{
//clear all notifications before setting a new one
[[UIApplication sharedApplication] cancelAllLocalNotifications];
//set a new LocalNotification
UILocalNotification *localNotification=localNotification =[[UILocalNotification alloc] init];
if(localNotification!=nil){
localNotification.fireDate=[NSDate dateWithTimeIntervalSinceNow:60]; //seconds
localNotification.timeZone=[NSTimeZone defaultTimeZone];
localNotification.alertBody=#"Reminder!";
localNotification.hasAction=YES; //fires didreceive event, opens app
localNotification.soundName=UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; }
}
-(void)clickedAlert{
NSLog(#"clicked alert");
[self.alarmSwitch setOn:NO animated:YES];
}
This has the desired effect of setting the "alarmSwitch" to "Off" (and thus canceling further notices), but the switch itself still shows in the view as "On" (green).
How can I flip the actual switch on the SettingsPage via code from my AppDelegate.m so that it behaves the same as if the user did it (i.e. changes it's visual and executes the connected method)?
As CrimsonChris mentioned, you seem to be making a new instance of SettingsPage every time, thus you're not seeing the change you want.
You could fire off an NSNotification,
[[NSNotificationCenter defaultCenter] postNotificationName:#"ClickedButtonAtIndex1" object:nil];
..and listen to it in your UIViewController.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleIndex1Clicked) name:#"ClickedButtonAtIndex1" object:nil];
with your UIViewController doing what it needs to in the selector method:
-(void)handleIndex1Clicked
{
[self.setPage.alarmSwitch setOn:NO animated:YES];
}
PS. I'd suggest having extern const NSStrings holding your observer names.
Hope that helps!
It looks like you're getting a new SettingsPage and then setting its alarmSwitch to "Off". What you probably want is to get the existing SettingsPage instead of making a new one with alloc init.
I have a UIAlertView which pops up with a message and a single button "Continue". What I would like for is that a Shake Gesture would also call the same code as hitting the continue button. However, I'm not sure if this is possible with the UIAlertView in iOS7 or if I have to create my own custom alert view.
I have tried implementing:
(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { ... }
and
canBecomeFirstResponder { return YES; }
But while they work normally with the main view set to becomeFirstResponder they don't respond once the UIAlertView pops up. So then, I tried implementing these in a Category for UIAlertView+MotionResponder.h to try to extend UIAlertView, but the motionEnded is not getting called even though canBecomeFirstResponder is correctly returning YES.
So should this work? Or do I have to implement my own alert view?
According to the documentation
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
The internal view hierarchy of UIAlertView is much more complicated than it appears and starting from iOS 7 it is not even added to any UIWindow, so it's no big surprise that it doesn't participate to the responder chain as you expect.
However, you might consider of implementing the motion recognition logic within your controller instead and to have it triggering the UIAlertView dismissal.
You just need to keep a reference to the UIAlertView instance, detect the shake gesture in the controller and send a –dismissWithClickedButtonIndex:animated: message to the alert, in order to dismiss it.
EDIT
The above technique won't work when the UIAlertView is presented on top of the current controller since it will intercept the shake gesture and the controller method will never be called.
To make it work, you have to detect the gesture at a window level. Here's a brief example of how you can do.
First we define a category over UIWindow that intercepts the shake gesture and broadcasts a notification (I'll just post the #implementation, since the #interface is empty)
#implementation UIWindow (Shake)
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"UIWindowDidShake" object:nil userInfo:nil];
}
}}
#end
then in the controller we register to the notification when showing the alert and we dismiss the alert when the notification arrives. Finally we can remove the controller from the observers as soon as the alert has been dismissed.
#property (nonatomic, strong) UIAlertView *alert;
...
- (IBAction)showAlert:(id)sender {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissAlert) name:#"UIWindowDidShake" object:nil];
self.alert = [[UIAlertView alloc] initWithTitle:nil message:#"An alert" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[self.alert show];
}
- (void)dismissAlert {
[self.alert dismissWithClickedButtonIndex:0 animated:YES];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIWindowDidShake" object:nil];
}
I have next code:
#implementation SplashViewVC
- (void)viewDidLoad
{
[super viewDidLoad];
self.splashView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Default.png"]];
self.activityIndicator.originY = 355.f;
[[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
NSInteger errorCode = [n.userInfo[#"errorCode"] integerValue];
[self.activityIndicator stopAnimating];
if (errorCode == ERROR_CODE_NO_CONNECTION) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Some problem with server" delegate:self cancelButtonTitle:#"try again" otherButtonTitles:nil];
[alertView show];
} else if (errorCode == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
[self downloadData];
}
- (void)downloadData
{
[self.activityIndicator startAnimating];
[[Server sharedServer] getMovieData];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self downloadData];
}
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewDidDisappear:animated];
}
#end
So I put breakpoints in begin of viewDidLoad method, in viewDidDisappear. When I launch app that first go to viewDidload, after downloading it is go to viewDidDisappear.
But during my app I again download data and post notification: NSDownloadComplete. And in this VC it is work, but I removed later using:
[[NSNotificationCenter defaultCenter] removeObserver:self]
This VC use viewDidLoad once in the beginning & can not again addObserver.
What is wrong?
EDIT
I try put addObserver method to viewWillAppear or viewWillDisappear - no results.
I add NSLog(#"addObserver"); before
[[NSNotificationCenter defaultCenter] addObserverForName...
in viewDidLoad
and write
- (void)viewDidDisappear:(BOOL)animated
{
NSLog(#"removeObserver");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewDidDisappear:animated];
}
In log I see:
2013-06-10 14:32:05.646 myApp[9390:c07] addObserver
2013-06-10 14:32:06.780 myApp[9390:c07] removeObserver
What wrong?
EDIT 2
you can see that observer must be removed but it again run block in addObserver method
Apart from add/remove observer calls not properly being balanced, at noted in the other answers, there is another problem.
Your code to remove the observer is wrong. For a block-based observer, the return value of addObserver must be given as argument to removeObserver. So you should add a property
#property(nonatomic, strong) id observer;
to the class. Then you add the observer with
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
// ...
}];
and remove it with
[[NSNotificationCenter defaultCenter] removeObserver:self.observer];
What e1985 is trying to expose is that your addObserver and removeObserver calls are not properly balanced. viewDidLoad is called only once after the VC initialization, but viewDidDisappear is called each time the view controller is moved off screen.
To resolve your issue you must balance your addObserver and removeObserver calls, either by making them in viewDidLoad and the other in dealloc, or - as e1985 suggested - in viewDidAppear: and viewDidDisappear:.
EDIT: Ok, so your problem comes from the fact that you are using addObserverForName:object:queue:usingBlock: which do not register self as observer (as addObserver:selector:name:object: would do if you pass self as first argument).
So in your case, [[NSNotificationCenter defaultCenter] removeObserver:self]; does nothing because self is not an observer. You should instead call removeObserver: on the return value of addObserverForName:object:queue:usingBlock:, as shown in the doc:
Return Value
An opaque object to act as the observer.
So your code should looks something like:
// header file .h
#interface SplashViewVC : UIViewController
#property (strong, nonatomic) id downloadCompleteObserver;
#end
// implementation file .m
#implementation SplashViewVC
- (void)viewDidLoad
{
[super viewDidLoad];
// [...] snip
self.downloadCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
NSInteger errorCode = [n.userInfo[#"errorCode"] integerValue];
[self.activityIndicator stopAnimating];
if (errorCode == ERROR_CODE_NO_CONNECTION) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Some problem with server" delegate:self cancelButtonTitle:#"try again" otherButtonTitles:nil];
[alertView show];
} else if (errorCode == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
[self downloadData];
}
// [...] snip
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self.downloadCompleteObserver];
[super dealloc];
}
#end
The pattern you are using is not correct. You should add the observer in viewDidAppear: and remove it in viewDidDisappear:.
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 ?
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];
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.
Edit: (Cédric) I have added a way to catch calls to delegate methods and remove the observer then to avoid multiple registrations to the notification center.
Everything bundled in a class in this github repo: https://github.com/sdarlington/WSLViewAutoDismiss
#import "UIAlertViewAutoDismiss.h"
#import <objc/runtime.h>
#interface UIAlertViewAutoDismiss () <UIAlertViewDelegate> {
id<UIAlertViewDelegate> __unsafe_unretained privateDelegate;
}
#end
#implementation UIAlertViewAutoDismiss
- (id)initWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
for (NSString *anOtherButtonTitle = otherButtonTitles; anOtherButtonTitle != nil; anOtherButtonTitle = va_arg(args, NSString *)) {
[self addButtonWithTitle:anOtherButtonTitle];
}
privateDelegate = delegate;
}
return self;
}
- (void)dealloc
{
privateDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[super dealloc];
}
- (void)setDelegate:(id)delegate
{
privateDelegate = delegate;
}
- (id)delegate
{
return privateDelegate;
}
- (void)show
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[super show];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[super dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
#pragma mark - UIAlertViewDelegate
// The code below avoids to re-implement all protocol methods to forward to the real delegate.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
struct objc_method_description hasMethod = protocol_getMethodDescription(#protocol(UIAlertViewDelegate), aSelector, NO, YES);
if (hasMethod.name != NULL) {
// The method is that of the UIAlertViewDelegate.
if (aSelector == #selector(alertView:didDismissWithButtonIndex:) ||
aSelector == #selector(alertView:clickedButtonAtIndex:))
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return privateDelegate;
}
else {
return [super forwardingTargetForSelector:aSelector];
}
}
#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.
A totally different approach is a recursive search.
Recursive function for your application delegate
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
Calling it from the applicationDidEnterBackground procedure
[self checkViews:application.windows];
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!)
As someone mentioned in a comment: the accepted answer isn't the best/cleanest one since iOS 4.0 when we have blocks! Here's how I do it:
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This alert will dismiss when application resigns active!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[alert dismissWithClickedButtonIndex:0 animated:NO];
}];
UIAlertView was deprecated in iOS 8 in favor of the UIAlertController. Unfortunately, this proves to be a tricky problem because the accepted solution won't work, as Apple explicitly doesn't support subclassing UIAlertController:
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
My solution is to simply traverse the view controller tree and dismiss all UIAlertControllers that you find. You can enable this globally by creating an extension of UIApplication and then calling it in the AppDelegate applicationDidEnterBackground method.
Try this (in Swift):
extension UIApplication
{
class func dismissOpenAlerts(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController)
{
//If it's an alert, dismiss it
if let alertController = base as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
//Check all children
if base != nil
{
for controller in base!.childViewControllers
{
if let alertController = controller as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
//Traverse the view controller tree
if let nav = base as? UINavigationController
{
dismissOpenAlerts(nav.visibleViewController)
}
else if let tab = base as? UITabBarController, let selected = tab.selectedViewController
{
dismissOpenAlerts(selected)
}
else if let presented = base?.presentedViewController
{
dismissOpenAlerts(presented)
}
}
}
And then in your AppDelegate:
func applicationDidEnterBackground(application: UIApplication)
{
UIApplication.dismissOpenAlerts()
}
I Have had solved this with the following code:
/* taken from the post above (Cédric)*/
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
NSLog(#"Class %#", [subview class]);
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
/*go to background delegate*/
- (void)applicationDidEnterBackground:(UIApplication *)application
{
for (UIWindow* window in [UIApplication sharedApplication].windows) {
NSArray* subviews = window.subviews;
[self checkViews:subviews];
}
}
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
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.
if you only have one or two specific alert windows you show (as do most apps), then you can just create an assign ivar to the alert:
#property (nonatomic, assign) UIAlertView* alertview;
Then, in the app delegate:
[self.viewController.alertview dismissWithClickedButtonIndex:[self.viewController.alertview cancelButtonIndex] animated:NO];
You can put this in applicationDidEnterBackground: or wherever you see fit. It closes the alert programmatically upon application exit. I've been doing this and it works great.
Create category on UIAlert View
Use http://nshipster.com/method-swizzling/
Swizzle "show" method
Keep track of alert view shown by keeping week references in array.
-
When you want to remove all data call Dismiss on saved alert views and empty an array.
An alternative solution, based on plkEL's, answer, where the observer is removed when the app is put in the background. If user dismisses the alert by pressing a button, the observer will still be active, but only until the app is put in the background (where the block is run - with an "nil alertView" - and the observer removed).
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:alertDelegate
cancelButtonTitle:cancelButtonText
otherButtonTitles:okButtonText, nil];
[alert show];
__weak UIAlertView *weakAlert = alert;
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[weakAlert dismissWithClickedButtonIndex:[weakAlert cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;
}];