I have created a signup form with a UIAlertController and used the method addTextFieldWithConfigurationHandler to add a text field. But there is a little problem.
When the form shows up, the keyboard and modal appear with a smooth animation. When closing the form, the modal disappears first, and then the keyboard disappears. This makes the keyboard make a sudden downward fall.
How can I make the modal and keyboard graciously disappear?
lazy var alertController: UIAlertController = { [weak self] in
let alert = UIAlertController(title: "Alert", message: "This is a demo alert", preferredStyle: .Alert)
alert.addTextFieldWithConfigurationHandler { textField in
textField.delegate = self
}
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
return alert
}()
#IBAction func alert() {
presentViewController(alertController, animated: true, completion: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
alertController.dismissViewControllerAnimated(true, completion: nil)
return true
}
You can set your view controller or other object as transitioning delegate of your UIAlertController (alert.transitioningDelegate) and make a custom animation for dismissing.
Code sample:
#interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning, UITextFieldDelegate>
#property (assign, nonatomic) NSTimeInterval keyboardAnimationDuration;
#property (assign, nonatomic) CGFloat keyboardHeight;
#property (nonatomic, strong) UIAlertController *alertController;
#property (nonatomic,strong) id <UIViewControllerTransitioningDelegate> transitioningDelegateForAlertController;
#end
#implementation ViewController
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self subscribeForKeyboardNotification];
}
#pragma mark - Keyboard notifications
- (void)subscribeForKeyboardNotification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillAppear:)
name:UIKeyboardWillShowNotification
object:nil];
}
- (void)keyboardWillAppear:(NSNotification *)notification {
self.keyboardAnimationDuration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
self.keyboardHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
}
#pragma mark - IBAction
- (IBAction)showAlertButtonPressed:(id)sender {
[self showAlert];
}
- (void)showAlert {
self.alertController = [UIAlertController alertControllerWithTitle:#"Alert"
message:#"This is a demo alert"
preferredStyle:UIAlertControllerStyleAlert];
__weak typeof(self) weakSelf = self;
[self.alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.delegate = weakSelf;
}];
self.transitioningDelegateForAlertController = self.alertController.transitioningDelegate;
self.alertController.transitioningDelegate = self;
[self.alertController addAction:[UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:self.alertController animated:YES completion:nil];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.alertController dismissViewControllerAnimated:YES completion:nil];
return YES;
}
#pragma mark - UIViewControllerTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source {
return [self.transitioningDelegateForAlertController animationControllerForPresentedController:presented
presentingController:presenting
sourceController:source];
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return self.keyboardAnimationDuration ?: 0.5;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController *destination = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
if ([destination isBeingPresented])
[self animatePresentation:transitionContext];
else
[self animateDismissal:transitionContext];
}
- (void)animatePresentation:(id <UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = transitionContext.containerView;
fromController.view.frame = container.bounds;
toController.view.frame = container.bounds;
toController.view.alpha = 0.0f;
[container addSubview:toController.view];
[fromController beginAppearanceTransition:NO animated:YES];
[UIView animateWithDuration:transitionDuration
animations:^{
toController.view.alpha = 1.0;
}
completion:^(BOOL finished) {
[fromController endAppearanceTransition];
[transitionContext completeTransition:YES];
}];
}
- (void)animateDismissal:(id <UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[toController beginAppearanceTransition:YES animated:YES];
[UIView animateWithDuration:transitionDuration
animations:^{
fromController.view.alpha = 0.0;
[fromController.view endEditing:YES];
CGRect frame = fromController.view.frame;
frame.origin.y += self.keyboardHeight / 2;
fromController.view.frame = frame;
}
completion:^(BOOL finished) {
[toController endAppearanceTransition];
[transitionContext completeTransition:YES];
}];
}
#end
Result:
P.S.: I used old alert's transitioning delegate for presentation because I can't reproduce an original animation. So animatePresentation: method is never used.
I had the exact same problem you had and found the solution incidentally. You probably don't need this anymore, but for the sake of others like me, here is the answer:
Swift:
override func canBecomeFirstResponder() -> Bool {
return true
}
Objective-C:
- (BOOL)canBecomeFirstResponder {
return true;
}
Just add this code in the view controller handling the alert. Only tested in swift.
Its pretty simple.
if your UIAlertController delegate are present in self View Controller. then you can do it in its delegate method for Dismiss AlertController. You can [youtTextField resignFirstResponder] in your UIAlertController object which have a button for dismiss it. (like OK or Cancel) so your presented KeyBoard will be dismissed.
I didn't tried it but It will work. but you have to handle textField and Alert correctly.
I assume the jumping down of the UIAlertController is if it dismisses after you press 'return' on the keyboard. If so, I have found a way for the Alert and keyboard to dismiss smoothly from a return action.
You will need declare the UIAlertController within the class file
#property (strong, nonatomic) UIAlertController *alertController;
And you will also need to use the UITextFieldDelegate with the viewController
When adding the textField to the UIAlertController this is where you will need to set the delegate of it to self. (weakSelf used as it is within a block)
#interface ViewController ()<UITextFieldDelegate>
Within the method you are auctioning the UIAlertController -
self.alertController = [UIAlertController alertControllerWithTitle:#"Alert" message:#"This is the message" preferredStyle:UIAlertControllerStyleAlert];
__weak typeof(self) weakSelf = self;
[self.alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.delegate = weakSelf;
}];
[self presentViewController:self.alertController animated:YES completion:nil];
Add this UITextField delegate method which will fire once the return button has been pressed on the keyboard. This means you can action for the UIAlertController to dismiss just prior to the keyboard dismissing, thus it makes it all work smoothly.
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[self.alertController dismissViewControllerAnimated:YES completion:nil];
return YES;
}
I've tested this and should work exactly the way you require.
Thanks,
Jim
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self.view endEditing:YES];
// or you can write [yourtextfield refignFirstResponder]
[alertView dismissWithClickedButtonIndex:buttonIndex animated:TRUE];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex==1) {
[[alertView textFieldAtIndex:0] resignFirstResponder];
} else {
[[alertView textFieldAtIndex:0] resignFirstResponder];
}
}
Use your button index (Ok or Cancel button index)
no need to do any thing you just have to implement this much of code, it works for me, no need to declare any kind of delegate methods
- (void)showAlert {
self.alertController = [UIAlertController alertControllerWithTitle:#"Alert"
message:#"Enter Name:"
preferredStyle:UIAlertControllerStyleAlert];
[self.alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
}];
[self.alertController addAction:[UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:self.alertController animated:YES completion:nil];
}
Swizzle viewWillDisappear method for UIAlertController, and perform resignFirstResponder on correspodent text field or call endEditing: on controller's view
I am using for this ReactiveCocoa:
let alert = UIAlertController(title: "", message: "", preferredStyle: .Alert)
alert.addTextFieldWithConfigurationHandler {
textField in
}
let textField = alert.textFields!.first!
alert.rac_signalForSelector(#selector(viewWillDisappear(_:)))
.subscribeNext {
_ in
textField.resignFirstResponder()
}
Related
Here is my View,
In this view, except Description field all are UITextfield and Description field is UITextView.
So what I want is that when I navigate to this view,
If I made some changes or modification then only save button will be enabled
And If I edited some fields' values and tried to go back to previous screen without saving it, following alert should pop up.
How to achieve it ? Thanks.
check following example, I hope it will help you.
#import "SampleViewController.h"
#interface SampleViewController ()
#end
#implementation SampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
_textview.delegate = self;
_secondButton.hidden = YES;
self.textview.layer.borderWidth = 2.0f;
self.textview.layer.borderColor = [[UIColor grayColor] CGColor];
self.navigationItem.hidesBackButton = YES;
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:#"< back" style:UIBarButtonItemStylePlain target:self action:#selector(back:)];
self.navigationItem.leftBarButtonItem = newBackButton;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)textViewDidBeginEditing:(UITextView *)textView{
_secondButton.hidden = NO;
}
- (void) back:(UIBarButtonItem *)sender {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Attention"
message:#"Are you sure want to discard this message."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
self.textview.text = nil;
[self.navigationController popViewControllerAnimated:YES];
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
NSLog(#"sandip");
[self.navigationController popViewControllerAnimated:YES];
}
- (IBAction)secondButton:(id)sender {
}
#end
Use a validator functions to validate the whole form by checking if any of the fields text count is > 0. To make your life easier, there are a few validators out there, which you can use such as, RAFieldValidator. Look out for more on github. Check these out https://github.com/search?utf8=%E2%9C%93&q=form+validator+ios&type=
Coming to triggering this function,
For Text field: check this out, UITextField text change event
For TextView: You need to set UITextView delegate and implement textViewDidChange: Method in it.
I want to reset the textfields of myview to empty when an actionsheet destructive button is pressed. Done button calls the actionsheet.
This is my actionsheet:
- (IBAction)submit:(id)sender {
UIActionSheet *sheet=[[UIActionSheet alloc]initWithTitle:#"Options" delegate:sender cancelButtonTitle:#"Cancel" destructiveButtonTitle:#"Reset" otherButtonTitles:#"Save", nil];
[sheet showInView:self.view];
}
And is used this method to reset:
- (void)sheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex==0)
{
self.textf1.text = #"";
}
}
But Nothing is happening.
Replace delegate:sender to delegate:self that's why delegates
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
are not getting call also update the delegate function, once you done just set all textFiled to .text= #"". i hope it will work
Also posted as Comment earlier.
delegate method you are using is worng
Please make your delegate to self
delegate:self
and Use this method
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
////check index and empty all textfields
}
You should set self as the delegate, and I think the new UIAlertController is more convenience to use, without any delegate:
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:#"Action Sheet" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *reset = [UIAlertAction actionWithTitle:#"Reset" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
self.textf1.text = #"";
}];
actionSheet.actions = #[reset, cancel];
[self presentViewController:actionSheet animated:YES completion:nil]
Lots of issues:
Change this line:
if (buttonIndex == 0)
to:
if (buttonIndex == sheet.destructiveButtonIndex)
You also need to pass self as the delegate instead of sender.
UIActionSheet *sheet= [[UIActionSheet alloc] initWithTitle:#"Options" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:#"Reset" otherButtonTitles:#"Save", nil];
And the name of the delegate method matters. You need:
- (void)actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)buttonIndex
See the docs for UIActionSheet. There are specific properties to get various button indexes. Use those over hardcoding index numbers.
Also note that UIAlertView is deprecated. You should be using UIAlertController unless you need to support iOS 7.
#import "ViewController.h"
#interface ViewController ()<UITextFieldDelegate,UIActionSheetDelegate>
{
UIActionSheet *sheet;
}
#property (weak, nonatomic) IBOutlet UITextField *txtText;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
//Button click
- (IBAction)OpenActionSheet:(id)sender
{
sheet=[[UIActionSheet alloc]initWithTitle:#"ActionSheetDemo" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:#"Reset" otherButtonTitles:#"Save", nil];
[sheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex==0)
{
_txtText.text=#"";
}
}
#end
I have 2 ViewControllers. After login it will move to the next screen. Then one logout button will be appear. After user press log out button it should return to login screen with an empty text field.
How would I do that? I tried all ways, but it doesn't seem to work.
My first ViewController:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.sampleDictionary = #{#"username":#"alex", #"password":#"1234"};
}
- (IBAction)loginTapped
{
if ([self.sampleDictionary[#"password"] isEqualToString:passwordField.text]) {
[self performSegueWithIdentifier:#"ss" sender:self];
} else {
// Alert message
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"wrong" message:#"Message" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *actionOk = [UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:actionOk];
[self presentViewController:alertController animated:YES completion:nil];
}
}
#end
This is my second ViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
}
- (IBAction)logout:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
In your ViewController remove the text for username and password fields in viewWillAppear.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
usernameField.text = #"";
passwordField.text = #"";
}
In your second ViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *logoutButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, 320, 50)];
[logoutButton setTitle:#"Log Out" forState:UIControlStateNormal];
[logoutButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[logoutButton setBackgroundColor:[UIColor blackColor]];
[logoutButton addTarget:self action:#selector(logout) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:logoutButton];
}
- (void)logout
{
[self dismissViewControllerAnimated:YES completion:nil];
}
So I have a view controller from which I have an action method sendSMS, which creates the SMS View Controller... Problem is, when I actually send the message, the controller will not dismiss, or it won't create any logs, so I guess the didFinishWithResult method is not even called. Big thanks goes to all of you!
So, my .m looks like this:
- (void)presentViewController:(UIViewController *)controller animated:(BOOL)animated onComplete:(void (^)(void))callback
{
MIKEAppDelegate *APP_DELEGATE = [UIApplication sharedApplication].delegate;
UIViewController *presentedModalVC = [APP_DELEGATE.window.rootViewController presentedViewController];
if (presentedModalVC) {
while (presentedModalVC.presentedViewController) {
presentedModalVC = presentedModalVC.presentedViewController;
}
[presentedModalVC presentViewController:controller animated:animated completion:callback];
} else {
[APP_DELEGATE.window.rootViewController presentViewController:controller animated:animated completion:callback];
}
}
-(void)sendSMS
{
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
if([MFMessageComposeViewController canSendText])
{
NSLog(#"SMS composer appeared");
controller.body = #"Testy Test";
controller.recipients = [NSArray arrayWithObjects:#"774252704", nil];
controller.messageComposeDelegate = self;
[controller presentViewController:controller animated:YES onComplete:nil];
}
}
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller
didFinishWithResult:(MessageComposeResult)result
{
// Notifies users about errors associated with the interface
switch (result)
{
case MessageComposeResultCancelled:
NSLog(#"Result: SMS sending canceled");
break;
case MessageComposeResultSent:
NSLog(#"Result: SMS sent");
break;
case MessageComposeResultFailed:
NSLog(#"Result: SMS sending failed");
break;
default:
NSLog(#"Result: SMS not sent");
break;
}
[self dismissViewControllerAnimated:YES completion:NULL];
}
EDIT 1:
My .h looks like this:
#import <UIKit/UIKit.h>
#import <MessageUI/MFMessageComposeViewController.h>
#interface MIKETableViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate, MFMessageComposeViewControllerDelegate>
-(void)viewWillAppear:(BOOL)animated;
-(void) sendSMS;
#end
EDIT 2:
Ok I tried to launch sendSMS method by pressing button, and everything works as it should, so there is a problem in that HOW I call the method. Thing is that I call the method from myCustomTableCell Class... Idea is, when I slide the cell of the screen It will call the sendSMS method. Please see .m file from my custom cell
-(void)panGestureRecognizer:(UIPanGestureRecognizer *)sender{
CGPoint translation = [sender translationInView:self];
MIKETableViewController *mainController = [[MIKETableViewController alloc] init];
//NSLog(#"Panned with translation point: %#", NSStringFromCGPoint(translation));
sender.view.center = CGPointMake(sender.view.center.x + translation.x,
sender.view.center.y);
CGPoint breakingPoint = CGPointMake(320,sender.view.center.y);
CGPoint startPoint = CGPointMake(150, sender.view.center.y);
CGPoint endPoint = CGPointMake(500, sender.view.center.y);
if (sender.view.center.x <= startPoint.x) {
sender.view.center = startPoint;
}
if (sender.state == UIGestureRecognizerStateEnded) {
if (sender.view.center.x >= breakingPoint.x) {
[UIView animateWithDuration:0.2
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
sender.view.center = endPoint;
}
completion:^(BOOL finished){
NSLog(#"Bought!");
self.timeLabel.text = timeLabelValue;
self.priceLabel.text = priceLabelValue;
self.infoLabel.text = infoLabelValue;
[mainController tableView:mainController.self.tableView didSelectRowAtIndexPath:mainController.self.tableView.indexPathForSelectedRow];
[mainController sendSMS];
}];
} else {
//recognizer.view.center = startPoint;
[UIView animateWithDuration:0.2
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
sender.view.center = startPoint;
}
completion:^(BOOL finished){
NSLog(#"Returned!");
}];
}
}
[sender setTranslation: CGPointZero inView: self];
}
You are dismissing self in the mail composer delegate method, instead of the mail view controller, change:
[self dismissViewControllerAnimated:YES completion:NULL];
to
[controller dismissViewControllerAnimated:YES completion:NULL];
It should work, But your code looks proper, so most probably you have not conformed the delegate <MFMessageComposeViewControllerDelegate>
#Janak Nirmal : Sorry, In hurry i didn't see the delegate method implementation.
Put the break point in the delegate method and see whether the control comes to that method.
In sendSMS method use this code to present :
[self presentViewController:controller animated:YES completion:nil];
now check. and only when send or cancel button pressed, the delegate will be called.
EDIT
The problem is because of that method :
presentViewController:animated:onComplete:
On which controller you present from the same controller you need to dismiss.
For example :
[self presentViewController:viewController animated:YES onComplete:nil];
[self dismissViewControllerAnimated:YES completion:nil];
or
[[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:controller animated:animated completion:callback];
[[UIApplication sharedApplication].delegate.window.rootViewController
dismissViewControllerAnimated:YES completion:nil];
EDIT
Import this in your .h file
#import <MessageUI/MessageUI.h>
Add this header file and check whether the delegate is being called.
EDIT
So you are saying that :
messageComposeViewController:didFinishWithResult:
this delegate method even now is not getting called on clicking send or cancel????
EDIT
instead of this line of code
[controller presentViewController:controller animated:YES onComplete:nil];
put this code and tell me whether the delegate method being called
UIViewController *rootViewController = [[[[UIApplication sharedApplication] delegate] window]rootViewController];
[rootViewController presentViewController:viewController animated:YES completion:nil];
EDIT
Only way is left out is, give me access to your code somewhere in VCS and i will help you out with the problem.
I have a Utils class which shows UIAlertView when certain notifications are triggered. Is there a way to dismiss any open UIAlertViews before showing a new one?
Currenty I am doing this when the app enters the background using
[self checkViews:application.windows];
on applicationDidEnterBackground
- (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];
}
}
}
This makes it easy on applicationDidEnterBackground as I can use application.windows
Can I use the AppDelegate or anything similar to get all the views, loop through them and dismiss any UIAlertViews?
for (UIWindow* window in [UIApplication sharedApplication].windows) {
NSArray* subviews = window.subviews;
if ([subviews count] > 0)
if ([[subviews objectAtIndex:0] isKindOfClass:[UIAlertView class]])
[(UIAlertView *)[subviews objectAtIndex:0] dismissWithClickedButtonIndex:[(UIAlertView *)[subviews objectAtIndex:0] cancelButtonIndex] animated:NO];
}
iOS6 compatible version:
for (UIWindow* w in UIApplication.sharedApplication.windows)
for (NSObject* o in w.subviews)
if ([o isKindOfClass:UIAlertView.class])
[(UIAlertView*)o dismissWithClickedButtonIndex:[(UIAlertView*)o cancelButtonIndex] animated:YES];
iOS7 compatible version:
I made a category interface that stores all instance in init method.
I know it's a very inefficient way.
#import <objc/runtime.h>
#import <objc/message.h>
#interface UIAlertView(EnumView)
+ (void)startInstanceMonitor;
+ (void)stopInstanceMonitor;
+ (void)dismissAll;
#end
#implementation UIAlertView(EnumView)
static BOOL _isInstanceMonitorStarted = NO;
+ (NSMutableArray *)instances
{
static NSMutableArray *array = nil;
if (array == nil)
array = [NSMutableArray array];
return array;
}
- (void)_newInit
{
[[UIAlertView instances] addObject:[NSValue valueWithNonretainedObject:self]];
[self _oldInit];
}
- (void)_oldInit
{
// dummy method for storing original init IMP.
}
- (void)_newDealloc
{
[[UIAlertView instances] removeObject:[NSValue valueWithNonretainedObject:self]];
[self _oldDealloc];
}
- (void)_oldDealloc
{
// dummy method for storing original dealloc IMP.
}
static void replaceMethod(Class c, SEL old, SEL new)
{
Method newMethod = class_getInstanceMethod(c, new);
class_replaceMethod(c, old, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
}
+ (void)startInstanceMonitor
{
if (!_isInstanceMonitorStarted) {
_isInstanceMonitorStarted = YES;
replaceMethod(UIAlertView.class, #selector(_oldInit), #selector(init));
replaceMethod(UIAlertView.class, #selector(init), #selector(_newInit));
replaceMethod(UIAlertView.class, #selector(_oldDealloc), NSSelectorFromString(#"dealloc"));
replaceMethod(UIAlertView.class, NSSelectorFromString(#"dealloc"), #selector(_newDealloc));
}
}
+ (void)stopInstanceMonitor
{
if (_isInstanceMonitorStarted) {
_isInstanceMonitorStarted = NO;
replaceMethod(UIAlertView.class, #selector(init), #selector(_oldInit));
replaceMethod(UIAlertView.class, NSSelectorFromString(#"dealloc"), #selector(_oldDealloc));
}
}
+ (void)dismissAll
{
for (NSValue *value in [UIAlertView instances]) {
UIAlertView *view = [value nonretainedObjectValue];
if ([view isVisible]) {
[view dismissWithClickedButtonIndex:view.cancelButtonIndex animated:NO];
}
}
}
#end
Start instance monitoring before using UIAlertView.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//...
//...
[UIAlertView startInstanceMonitor];
return YES;
}
Call dismissAll before showing another.
[UIAlertView dismissAll];
It's better using a singleton pattern if you can control all UIAlertViews.
But in my case, I need this code for closing javascript alert dialog in a UIWebView.
Since UIAlertView is deprecated in iOS8 in favor of UIAlertController (which is a UIViewController, presented modally), you can't preset 2 alerts at the same time (from the same viewController at least). The second alert will simply not be presented.
I wanted to partially emulate UIAlertView's behavior, as well as prevent showing multiple alerts at once. Bellow is my solution, which uses window's rootViewController for presenting alerts (usually, that is appDelegate's navigation controller). I declared this in AppDelegate, but you can put it where you desire.
If you encounter any sorts of problems using it, please report here in comments.
#interface UIViewController (UIAlertController)
// these are made class methods, just for shorter semantics. In reality, alertControllers
// will be presented by window's rootViewController (appdelegate.navigationController)
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSArray *)otherButtonTitles
handler:(void (^)(NSInteger buttonIndex))block;
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle;
#end
#implementation UIViewController (UIAlertController)
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
{
return [self presentAlertWithTitle:title message:message cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil handler:nil];
}
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSArray *)otherButtonTitles
handler:(void (^)(NSInteger buttonIndex))block
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
if (block)
block(0);
}];
[alert addAction:cancelAction];
[otherButtonTitles enumerateObjectsUsingBlock:^(NSString *title, NSUInteger idx, BOOL *stop) {
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if (block)
block(idx + 1); // 0 is cancel
}];
[alert addAction:action];
}];
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
UIViewController *rootViewController = appDelegate.window.rootViewController;
if (rootViewController.presentedViewController) {
[rootViewController dismissViewControllerAnimated:NO completion:^{
[rootViewController presentViewController:alert animated:YES completion:nil];
}];
} else {
[rootViewController presentViewController:alert animated:YES completion:nil];
}
return alert;
}
#end