Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITextField: move view when keyboard appears

I'm currently working on an iPhone application with a single view, which has multiple UITextFields for input. When the keyboard shows, it overlays the bottom textfields. So I added the corresponding textFieldDidBeginEditing: method, to move the view up, which works great:

- (void)textFieldDidBeginEditing:(UITextField *)textField {     if ( ( textField != inputAmount ) && ( textField != inputAge ) ) {         NSTimeInterval animationDuration = 0.300000011920929;         CGRect frame = self.view.frame;         frame.origin.y -= kOFFSET_FOR_KEYBOARD;         frame.size.height += kOFFSET_FOR_KEYBOARD;         [UIView beginAnimations:@"ResizeForKeyboard" context:nil];         [UIView setAnimationDuration:animationDuration];         self.view.frame = frame;         [UIView commitAnimations];           } } 

This method checks, if the source of the message is one of the textfields that are visible when the keyboard shows, and if not, it moves the view up.

I also added the textFieldDidEndEnditing: method, which moves the view down again (and updates some model objects according to the changed input):

- (void)textFieldDidEndEditing:(UITextField *)textField {     if ( ( textField != inputMenge ) && ( textField != inputAlter ) ) {         NSTimeInterval animationDuration = 0.300000011920929;         CGRect frame = self.view.frame;         frame.origin.y += kOFFSET_FOR_KEYBOARD;         frame.size.height -= kOFFSET_FOR_KEYBOARD;         [UIView beginAnimations:@"ResizeForKeyboard" context:nil];         [UIView setAnimationDuration:animationDuration];         self.view.frame = frame;         [UIView commitAnimations];           }     // Additional Code } 

However, this solution has a simple flaw: When I finish editing one of the "hidden" textfields and touch another textfield, the keyboard vanishes, the view moves down, the view moves up again and the keyboard reappears.

Is there any possibility to keep the keyboard from vanishing and reappearing between two edits (of the "hidden" textfields - so that the view only moves when the selected textfield changes from one that would be hidden by the keyboard to one that would not be hidden)?

like image 929
ComSubVie Avatar asked Nov 21 '09 15:11

ComSubVie


People also ask

How do I move the view up on keyboard in IOS?

The best way to do this is to place your content inside a UIScrollView, then adjust the scroll view's contentInset property by the height of the keyboard when it's shown. Absolutely do not assume the keyboard height--use the value from the "keyboard will show" notification.

How do you dismiss a keyboard in Swift?

Via Tap Gesture This is the quickest way to implement keyboard dismissal. Just set a Tap gesture on the main View and hook that gesture with a function which calls view. endEditing . Causes the view (or one of its embedded text fields) to resign the first responder status.


2 Answers

This solution is based on ComSubVie's one.

Advantages:

  • It supports device rotation - works for all orientations;
  • It doesn't hardcode the values for animation duration and curve, it reads them from the keyboard notification;
  • It utilizes UIKeyboardWillShowNotification instead of UIKeyboardDidShowNotification to sync keyboard animation and custom actions;
  • It doesn't use the deprecated UIKeyboardBoundsUserInfoKey;
  • It handles keyboard resize due to pressing the International key;
  • Fixed memory leak by unregistering for keyboard events;
  • All keyboard handling code is encapsulated in a separate class - KBKeyboardHandler;
  • Flexibility - KBKeyboardHandler class may be easy extended / modified to better suit specific needs;

Limitations:

  • Works for iOS 4 and above, it needs small modifications to support older versions;
  • It works for applications with a single UIWindow. If you use multiple UIWindows, you may need to modify retrieveFrameFromNotification: method.

Usage:

Include KBKeyboardHandler.h, KBKeyboardHandler.m and KBKeyboardHandlerDelegate.h in your project. Implement the KBKeyboardHandlerDelegate protocol in your view controller - it consists of a single method, which will be called when keyboard is shown, hidden or its size is changed. Instantiate the KBKeyboardHandler and set its delegate (typically self). See sample MyViewController below.

KBKeyboardHandler.h:

#import <Foundation/Foundation.h> #import <UIKit/UIKit.h>  @protocol KBKeyboardHandlerDelegate;  @interface KBKeyboardHandler : NSObject  - (id)init;  // Put 'weak' instead of 'assign' if you use ARC @property(nonatomic, assign) id<KBKeyboardHandlerDelegate> delegate;  @property(nonatomic) CGRect frame;  @end 

KBKeyboardHandler.m:

#import "KBKeyboardHandler.h" #import "KBKeyboardHandlerDelegate.h"  @implementation KBKeyboardHandler  - (id)init {     self = [super init];     if (self)     {         [[NSNotificationCenter defaultCenter] addObserver:self                                                  selector:@selector(keyboardWillShow:)                                                      name:UIKeyboardWillShowNotification                                                    object:nil];          [[NSNotificationCenter defaultCenter] addObserver:self                                                  selector:@selector(keyboardWillHide:)                                                      name:UIKeyboardWillHideNotification                                                    object:nil];     }      return self; }  - (void)dealloc {     [[NSNotificationCenter defaultCenter] removeObserver:self];     [super dealloc]; }  @synthesize delegate; @synthesize frame;  - (void)keyboardWillShow:(NSNotification *)notification {     CGRect oldFrame = self.frame;         [self retrieveFrameFromNotification:notification];      if (oldFrame.size.height != self.frame.size.height)     {         CGSize delta = CGSizeMake(self.frame.size.width - oldFrame.size.width,                                   self.frame.size.height - oldFrame.size.height);         if (self.delegate)             [self notifySizeChanged:delta notification:notification];     } }  - (void)keyboardWillHide:(NSNotification *)notification {     if (self.frame.size.height > 0.0)     {         [self retrieveFrameFromNotification:notification];         CGSize delta = CGSizeMake(-self.frame.size.width, -self.frame.size.height);          if (self.delegate)             [self notifySizeChanged:delta notification:notification];     }      self.frame = CGRectZero; }  - (void)retrieveFrameFromNotification:(NSNotification *)notification {     CGRect keyboardRect;     [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];     self.frame = [[UIApplication sharedApplication].keyWindow.rootViewController.view convertRect:keyboardRect fromView:nil]; }  - (void)notifySizeChanged:(CGSize)delta notification:(NSNotification *)notification {     NSDictionary *info = [notification userInfo];      UIViewAnimationOptions curve;     [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];      NSTimeInterval duration;     [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];      void (^action)(void) = ^{         [self.delegate keyboardSizeChanged:delta];     };      [UIView animateWithDuration:duration                           delay:0.0                         options:curve                      animations:action                      completion:nil];     }  @end 

KBKeyboardHandlerDelegate.h:

@protocol KBKeyboardHandlerDelegate  - (void)keyboardSizeChanged:(CGSize)delta;  @end 

Sample MyViewController.h:

@interface MyViewController : UIViewController<KBKeyboardHandlerDelegate> ... @end 

Sample MyViewController.m:

@implementation MyViewController {     KBKeyboardHandler *keyboard; }  - (void)dealloc {     keyboard.delegate = nil;     [keyboard release];     [super dealloc]; }  - (void)viewDidLoad {     [super viewDidLoad];     keyboard = [[KBKeyboardHandler alloc] init];     keyboard.delegate = self; }  - (void)viewDidUnload {     [super viewDidUnload];     keyboard.delegate = nil;     [keyboard release];     keyboard = nil; }  - (void)keyboardSizeChanged:(CGSize)delta {     // Resize / reposition your views here. All actions performed here      // will appear animated.     // delta is the difference between the previous size of the keyboard      // and the new one.     // For instance when the keyboard is shown,      // delta may has width=768, height=264,     // when the keyboard is hidden: width=-768, height=-264.     // Use keyboard.frame.size to get the real keyboard size.      // Sample:     CGRect frame = self.view.frame;     frame.size.height -= delta.height;     self.view.frame = frame; } 

UPDATE: Fixed iOS 7 warning, thanks @weienv.

like image 95
Vladimir Grigorov Avatar answered Oct 03 '22 21:10

Vladimir Grigorov


I just solved this problem. The solution is a combination of a UIKeyboardDidShowNotification and UIKeyboardDidHideNotification observer with the above textFieldDidBeginEditing: and textFieldDidEndEditing: methods.

You need three additional variables, one to store the current selected UITextField (which I have named activeField), one to indicate if the current view has been moved, and one to indicate if the keyboard is displayed.

This is how the two UITextField delegate methods look now:

- (void)textFieldDidBeginEditing:(UITextField *)textField {     activeField = textField; }  - (void)textFieldDidEndEditing:(UITextField *)textField {     activeField = nil;     // Additional Code } 

When the view is loaded, the following two observers are created:

- (void)viewDidLoad {     // Additional Code     [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(keyboardWasShown:)                                                  name:UIKeyboardDidShowNotification                                                object:nil];     [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(keyboardWasHidden:)                                                  name:UIKeyboardDidHideNotification                                                object:nil]; } 

And the corresponding methods are implemented as follows:

- (void)keyboardWasShown:(NSNotification *)aNotification {     if ( keyboardShown )         return;      if ( ( activeField != inputAmount ) && ( activeField != inputAge ) ) {         NSDictionary *info = [aNotification userInfo];         NSValue *aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];         CGSize keyboardSize = [aValue CGRectValue].size;          NSTimeInterval animationDuration = 0.300000011920929;         CGRect frame = self.view.frame;         frame.origin.y -= keyboardSize.height-44;         frame.size.height += keyboardSize.height-44;         [UIView beginAnimations:@"ResizeForKeyboard" context:nil];         [UIView setAnimationDuration:animationDuration];         self.view.frame = frame;         [UIView commitAnimations];          viewMoved = YES;     }      keyboardShown = YES; }  - (void)keyboardWasHidden:(NSNotification *)aNotification {     if ( viewMoved ) {         NSDictionary *info = [aNotification userInfo];         NSValue *aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];         CGSize keyboardSize = [aValue CGRectValue].size;          NSTimeInterval animationDuration = 0.300000011920929;         CGRect frame = self.view.frame;         frame.origin.y += keyboardSize.height-44;         frame.size.height -= keyboardSize.height-44;         [UIView beginAnimations:@"ResizeForKeyboard" context:nil];         [UIView setAnimationDuration:animationDuration];         self.view.frame = frame;         [UIView commitAnimations];          viewMoved = NO;     }      keyboardShown = NO; } 

This code works now as expected. The keyboard is only dismissed when the Done button is pressed, otherwise it stays visible and the view is not moved around.

As an additional note, I think it is possible to get the animationDuration dynamically by asking the NSNotification object, since I have already played with a similar solution but didn't get it to work (which it does now).

like image 20
ComSubVie Avatar answered Oct 03 '22 20:10

ComSubVie