UITextField: move view when keyboard appears
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 ofUIKeyboardDidShowNotification
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 modifyretrieveFrameFromNotification:
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];
}
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 & helicopters. I also do some sports like swimming, diving or hiking.
Updated on July 09, 2022Comments
-
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 about 13 yearsWhat 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 about 13 yearsJust a note to those who might not catch this. You need add to your .h file: UITextField *activeField; BOOL keyboardShown; BOOL viewMoved;
-
Jay Peyer almost 13 yearsGood, though instead of hardcoding the
animationDuration
andanimationCurve
values, you should use theUIKeyboardAnimationDurationUserInfoKey
andUIKeyboardAnimationCurveUserInfoKey
userInfo values. -
jeswang over 11 yearsUIKeyboardDidShowNotification -> UIKeyboardWillShowNotification ?
-
Dejell over 11 yearsYou are missing the viewDidUnload
-
Dejell over 11 yearsI get keyboardDidShow:]: unrecognized selector sent to instance I am using UITextView not textField
-
Johan Karlsson over 11 yearsReal nifty solution. Works great!
-
SmileBot about 11 yearsUIKeyboardBoundsUserInfoKey is deprecated. Also @Odelya, using viewDidUnload is deprecated.
-
Olcay Ertaş over 10 yearsWhat are inputAmount and inputAge?
-
weienw about 10 yearsStill 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 over 9 yearsIt 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 about 9 yearswhat is inputAmount and inputAge ??
-
Basanth almost 8 yearsAwesome! Works like a charm!
-
Radu about 4 years0.300000011920929 is that some Taliban secret code?