UITextField: move view when keyboard appears

47,670

Solution 1

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.

Solution 2

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).

Solution 3

This view controller must be UITextView Delegate and you must set self.textview.delegate = self in viewdidload

 -(void) textViewDidBeginEditing:(UITextView *)textView
    {
        NSLog(@"%f",self.view.frame.origin.y);
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.25f];
        CGRect frame = self.view.frame;
        frame.origin.y =frame.origin.y -204;
        [self.view setFrame:frame];
        [UIView commitAnimations];
    }

-(void) textViewDidEndEditing:(UITextView *)textView
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.25f];
    CGRect frame = self.view.frame;
    frame.origin.y = frame.origin.y + 204;
    [self.view setFrame:frame];
    [UIView commitAnimations];
}
Share:
47,670
sravis
Author by

sravis

I'm currently studying (at university) and teaching (at a higher technical school) computer science. When I'm not in front of my computer I like to build and fly model airplanes &amp; helicopters. I also do some sports like swimming, diving or hiking.

Updated on July 09, 2022

Comments

  • sravis
    sravis almost 2 years

    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)?

  • memmons
    memmons about 13 years
    What happens if the user brings the keyboard up in landscape mode and then rotates to portrait mode? The keyboard height is different, which means you will move the frame.origin.y an incorrect amount.
  • Michael Morrison
    Michael Morrison about 13 years
    Just a note to those who might not catch this. You need add to your .h file: UITextField *activeField; BOOL keyboardShown; BOOL viewMoved;
  • Jay Peyer
    Jay Peyer almost 13 years
    Good, though instead of hardcoding the animationDuration and animationCurve values, you should use the UIKeyboardAnimationDurationUserInfoKey and UIKeyboardAnimationCurveUserInfoKey userInfo values.
  • jeswang
    jeswang over 11 years
    UIKeyboardDidShowNotification -> UIKeyboardWillShowNotification ?
  • Dejell
    Dejell over 11 years
    You are missing the viewDidUnload
  • Dejell
    Dejell over 11 years
    I get keyboardDidShow:]: unrecognized selector sent to instance I am using UITextView not textField
  • Johan Karlsson
    Johan Karlsson over 11 years
    Real nifty solution. Works great!
  • SmileBot
    SmileBot about 11 years
    UIKeyboardBoundsUserInfoKey is deprecated. Also @Odelya, using viewDidUnload is deprecated.
  • Olcay Ertaş
    Olcay Ertaş over 10 years
    What are inputAmount and inputAge?
  • weienw
    weienw about 10 years
    Still works in iOS 7! You do get a warning because [UIView animateWithDuration:...] takes a UIViewAnimationOptions instead of UIViewAnimationCurve, so changing that type clears up that implicit cast. Thanks for the lightweight solution.
  • Jeroen
    Jeroen over 9 years
    It doesn't work for me on iOS 7. I changed UIViewAnimationCurve to UIViewAnimationOptions, and also the "assign" to "weak". I'm using Xcode 6, has something changed?
  • Ravi Ojha
    Ravi Ojha about 9 years
    what is inputAmount and inputAge ??
  • Basanth
    Basanth almost 8 years
    Awesome! Works like a charm!
  • Radu
    Radu about 4 years
    0.300000011920929 is that some Taliban secret code?