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 tested this on both an iPad and the iPad Simulator and after one successful shake, the motionBegan method is never called. The following is a snippet of my program.
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(event.type == UIEventSubtypeMotionShake)
{
NSLog(#"Shake event occurred.");
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"Information" message:#"Some message here" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.tag = shakeTag;
[alert show];
}
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:NO];
[self becomeFirstResponder];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:NO];
}
-(void)viewDidDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewDidDisappear:NO];
}
What exactly is preventing the motionBegan method from occurring again? I would like the UIAlertView to be presented exactly once for the first shake and dismissed for all subsequent shakes. I have a hunch that the First Responder is still attached to the UIAlertView which is preventing the motionBegan method from being called again.
Update: In my corresponding UIView, there is an UIActionSheet that is created. which is called and implemented in my UIView) and I trigger the motionBegan method (which is in my UIViewController) at the same time which the UIActionSheet is displayed on the screen, the problem where the motionBegan method no longer being able to be called exists.
Afterwards, the UIAlertView is dismissed from any button selection, the motionBegan method no longer is called but the UIActionSheet works perfectly fine. There is no firstResponder assignment in UIView and only the "canBecomeFirstResponder" exists in the UIViewController. Any ideas?
Since that code is in your viewController, remove all the stuff related to responder chain. You don't need it actually. It is made automatically for you. More precisely you can remove:
- (BOOL)canBecomeFirstResponder // remove all that method
{
return YES;
}
[self becomeFirstResponder]; // remove this line
...
[self resignFirstResponder]; // remove this line
...
and remove this as well all that method
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
Call your alertView stuff in motionEnd instead motionBegan. It could be better.
I am making an app that should support iOS versions from iOS5 onwards. It uses a UIAlertView, and if it's visible when the user presses the home button, I would like it to be dismissed before the user returns to the app (i.e. it's gone when the app is re-opened using multitasking). All methods in the app delegate show it as not visible (isVisible=NO) even if it is still visible when reopened. Is there a way to do this?
Thanks.
Or you inherit your class from UIAlertView and add NSNotification observer for UIApplicationWillResignActiveNotification and when notification occurs call to alertview method dismissWithClickedButtonIndex:
Example:
.h file
#import <UIKit/UIKit.h>
#interface ADAlertView : UIAlertView
#end
.m file
#import "ADAlertView.h"
#implementation ADAlertView
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id) initWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dismiss:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return self;
}
- (void) dismiss:(NSNotification *)notication {
[self dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:YES];
}
#end
With your own class inherited from UIAlertView you are need not to store link to alertview or something else, only one thing that you must do its replace UIAlertView to ADAlertView (or any other class name).
Feel free to use this code example (if you are not using ARC, you should add to the dealloc method [super dealloc] after [[NSNotificatioCenter defaultCenter] removeObserver:self])
Keep a reference to the displayed UIAlertView in your app delegate. When you show the alert, set the reference; when the alert is dismissed, nil out the reference.
In your app delegate's applicationWillResignActive: or applicationDidEnterBackground: method, call the dismissWithClickedButtonIndex:animated: method on the reference to the alert view. This would take care of dismissing it on pressing the "home" button.
Keep in mind that applicationWillResignActive: will be called for things such as phone calls, so you need to decide if you'd like to dismiss the alert in cases like that or if you should keep it up through the phone call.
I am building a converter app. In the main screen I have a text field to input numbers and below the text field a picker view will allow users to select conversion parameters, (for example kg to g).
I can hide the keyboard when user click the background by using the following method
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.enterInput resignFirstResponder];
but when I touch the picker view the keyboard is not hiding.
My question is how to dismiss the keyboard when a user touches the picker view.
Got a solution
1) First Create a hidden roundRect botton and change the type to custom (fit the size of the picker).
2) Create a touch up inside action
- (IBAction)hiddenButtonToHideKeyboard:(id)sender {
[self.enterInput resignFirstResponder];
}
3) Create a keyboard appear notification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(onKeyboardAppear:) name:UIKeyboardWillShowNotification object:nil];
4) Create a keyboard disappear notification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
5) Make the button visible when keyboard is appeared
-(void)onKeyboardAppear:(NSNotification *)notification
{
hiddenButtonToHideKeyboard.hidden=NO;
}
6) Hide the button when the keyboard is disappeared
-(void)onKeyboardHide:(NSNotification *)notification
{
hiddenButtonToHideKeyboard.hidden=YES;
}
5) done
I dont think it is a perfect solution but it works for me :)
I use this in my code. I climb the responder chain and reassign the responder. I put this in my method that shows the pickerView. So far no unexpected issues. Seems to work if a keyboard was showing and seems not to crash if there wasn't a keyboard showing.
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
Here you go. UIPickerViews are a fairly complex system of nested UIViews which is why you weren't getting any response from the touchesBegan:withEvent: method. What you can do is create your UIPickerView subclass as follows:
//
// MyPickerView.h
//
#import <UIKit/UIKit.h>
// Protocol Definition that extends UIPickerViewDelegate and adds a method to indicate a touch
#protocol MyPickerViewDelegate <UIPickerViewDelegate>
// This is the method we'll call when we've received a touch. Our view controller should implement it and hide the keyboard
- (void)pickerViewDidReceiveTouch:(UIPickerView *)pickerView;
#end
#interface MyPickerView : UIPickerView
// We're redefining delegate to require conformity to the MyPickerViewDelegate protocol we just made
#property (nonatomic, weak) id <MyPickerViewDelegate>delegate;
#end
//
// MyPickerView.m
//
#import "MyPickerView.h"
#implementation MyPickerView
#synthesize delegate = _myPickerViewDelegate; // We changed the data type of delegate as it was declared in the superclass so it's important to link it to a differently named backing variable
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// We make sure to call the super method so all standard functionality is preserved
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView) {
// This will be true if the hit was inside of the picker
[_myPickerViewDelegate pickerViewDidReceiveTouch:self];
}
// Return our results, again as part of preserving our superclass functionality
return hitView;
}
#end
Then in your ViewController change it to conform to <MyPickerViewDelegate> instead of <UIPickerViewDelegate>. This is ok since MyPickerViewDelegate inherits from UIPickerViewDelegate and will pass through the standard UIPickerViewDelegate methods.
Finally implement pickerViewDidReceiveTouch: in your view controller:
- (void)pickerViewDidReceiveTouch:(UIPickerView *)pickerView {
[enterInput resignFirstResponder];
}
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;
}];