15

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
}

presenting and dismissing

Thons
  • 285
  • 2
  • 8

8 Answers8

15

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:

enter image description here

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.

Community
  • 1
  • 1
Vlad
  • 7,199
  • 2
  • 25
  • 32
  • Really instructive, thank you. Could you please post the sample code of your idea? – Thons Apr 12 '15 at 14:11
  • Please try the code in my answer and tell me what kind of animation do you expect. – Vlad Apr 12 '15 at 14:15
  • I'm looking forward to the animation that when the alert disappears, the keyboard disappears simultaneously , just like they appeared together. – Thons Apr 12 '15 at 14:36
9

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.

wtoh
  • 200
  • 1
  • 7
2

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.

Sarat Patel
  • 856
  • 13
  • 32
  • 1
    Sorry I don't understand this. Can you provide a small snippet of code? – Randomblue Apr 11 '15 at 04:27
  • @Randomblue in iOS when you write [textfield resignFirstResponder] then it resign from first responder and hide keyboard. So in alertdelegate method you may write [textfield resignFirstResponder] so keyboard will hide and alert also dismiss.. – Bhoomi Jagani Apr 11 '15 at 10:13
1

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

Jim Tierney
  • 4,078
  • 3
  • 27
  • 48
  • Thank you, @Jim Tierney. I used your code to re-edit the question, and the result is shown in the latter part of the screenshot. – Thons Apr 12 '15 at 14:17
0
   - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

   { 
       [self.view endEditing:YES];
      // or you can write [yourtextfield refignFirstResponder]
       [alertView dismissWithClickedButtonIndex:buttonIndex animated:TRUE];
   }
Bhoomi Jagani
  • 2,413
  • 18
  • 24
  • 1
    This delegate method you refer to is for UIAlertview, which UIAlertController doesn't use as it's dismissal would be handled within the UIAction block used by the UIAlertController – Jim Tierney Apr 11 '15 at 10:35
0
- (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)

Kiran Patil
  • 402
  • 5
  • 13
0

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];

}

Patel Jigar
  • 2,141
  • 1
  • 23
  • 30
0

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()
             }
Igor Palaguta
  • 3,579
  • 2
  • 20
  • 32