How can I make a UITextField move up when the keyboard is present - on starting to edit?
With the iOS SDK:
I have a UIView
with UITextField
s that bring up a keyboard. I need it to be able to:
-
Allow scrolling of the contents of the
UIScrollView
to see the other text fields once the keyboard is brought up -
Automatically "jump" (by scrolling up) or shortening
I know that I need a UIScrollView
. I've tried changing the class of my UIView
to a UIScrollView
, but I'm still unable to scroll the textboxes up or down.
Do I need both a UIView
and a UIScrollView
? Does one go inside the other?
What needs to be implemented in order to automatically scroll to the active text field?
Ideally as much of the setup of the components as possible will be done in Interface Builder. I'd like to only write code for what needs it.
Note: the UIView
(or UIScrollView
) that I'm working with is brought up by a tabbar (UITabBar
), which needs to function as normal.
I am adding the scroll bar just for when the keyboard comes up. Even though it's not needed, I feel like it provides a better interface because then the user can scroll and change textboxes, for example.
I've got it working where I change the frame size of the UIScrollView
when the keyboard goes up and down. I'm simply using:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
//Keyboard becomes visible
scrollView.frame = CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50); // Resize
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
// Keyboard will hide
scrollView.frame = CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height + 215 - 50); // Resize
}
However, this doesn't automatically "move up" or center the lower text fields in the visible area, which is what I would really like.
Solution 1:
You will only need a
ScrollView
if the contents you have now do not fit in the iPhone screen. (If you are adding theScrollView
as the superview of the components just to make theTextField
scroll up when keyboard comes up, then it's not needed.)The standard way to prevent the
TextField
s from being covered by the keyboard is to move the view up/down whenever the keyboard is shown.
Here is some sample code:
#define kOFFSET_FOR_KEYBOARD 80.0
-(void)keyboardWillShow {
// Animate the current view out of the way
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
-(void)keyboardWillHide {
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
if ([sender isEqual:mailTf])
{
//move the main view, so that the keyboard does not hide it.
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
}
}
//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3]; // if you want to slide up the view
CGRect rect = self.view.frame;
if (movedUp)
{
// 1. move the view's origin up so that the text field that will be hidden come above the keyboard
// 2. increase the size of the view so that the area behind the keyboard is covered up.
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
}
else
{
// revert back to the normal state.
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
self.view.frame = rect;
[UIView commitAnimations];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
Solution 2:
I was also having a lot of issue with a UIScrollView
composing of multiple UITextFields
, of which, one or more of them would get obscured by the keyboard when they are being edited.
Here are some things to consider if your UIScrollView
is not properly scrolling.
1) Ensure that your contentSize is greater than the UIScrollView
frame size. The way to understand UIScrollViews
is that the UIScrollView
is like a viewing window on the content defined in the contentSize. So when in order for the UIScrollview
to scroll anywhere, the contentSize must be greater than the UIScrollView
. Else, there is no scrolling required as everything defined in the contentSize is already visible. BTW, default contentSize = CGSizeZero
.
2) Now that you understand that the UIScrollView
is really a window into your "content", the way to ensure that the keyboard is not obscuring your UIScrollView's
viewing "window" would be to resize the UIScrollView
so that when the keyboard is present, you have the UIScrollView
window sized to just the original UIScrollView
frame.size.height minus the height of the keyboard. This will ensure that your window is only that small viewable area.
3) Here's the catch: When I first implemented this I figured I would have to get the CGRect
of the edited textfield and call UIScrollView's
scrollRecToVisible method. I implemented the UITextFieldDelegate
method textFieldDidBeginEditing
with the call to the scrollRecToVisible
method. This actually worked with a strange side effect that the scrolling would snap the UITextField
into position. For the longest time I couldn't figure out what it was. Then I commented out the textFieldDidBeginEditing
Delegate method and it all work!!(???). As it turned out, I believe the UIScrollView
actually implicitly brings the currently edited UITextField
into the viewable window implicitly. My implementation of the UITextFieldDelegate
method and subsequent call to the scrollRecToVisible
was redundant and was the cause of the strange side effect.
So here are the steps to properly scroll your UITextField
in a UIScrollView
into place when the keyboard appears.
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:self.view.window];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:self.view.window];
keyboardIsShown = NO;
//make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
CGSize scrollContentSize = CGSizeMake(320, 345);
self.scrollView.contentSize = scrollContentSize;
}
- (void)keyboardWillHide:(NSNotification *)n
{
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
// resize the scrollview
CGRect viewFrame = self.scrollView.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
viewFrame.size.height += (keyboardSize.height - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = NO;
}
- (void)keyboardWillShow:(NSNotification *)n
{
// This is an ivar I'm using to ensure that we do not do the frame size adjustment on the `UIScrollView` if the keyboard is already shown. This can happen if the user, after fixing editing a `UITextField`, scrolls the resized `UIScrollView` to another `UITextField` and attempts to edit the next `UITextField`. If we were to resize the `UIScrollView` again, it would be disastrous. NOTE: The keyboard notification will fire even when the keyboard is already shown.
if (keyboardIsShown) {
return;
}
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
// resize the noteView
CGRect viewFrame = self.scrollView.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = YES;
}
- Register for the keyboard notifications at
viewDidLoad
- Unregister for the keyboard nofitications at
viewDidUnload
- Ensure that the
contentSize
is set and greater than yourUIScrollView
atviewDidLoad
-
Shrink the
UIScrollView
when the keyboard is present -
Revert back the
UIScrollView
when the keyboard goes away. - Use an ivar to detect if the keyboard is already shown on the screen since the keyboard notifications are sent each time a
UITextField
is tabbed even if the keyboard is already present to avoid shrinking theUIScrollView
when it's already shrunk
One thing to note is that the UIKeyboardWillShowNotification
will fire even when the keyboard is already on the screen when you tab on another UITextField
. I took care of this by using an ivar to avoid resizing the UIScrollView
when the keyboard is already on the screen. Inadvertently resizing the UIScrollView
when the keyboard is already there would be disastrous!
Hope this code saves some of you a lot of headache.
Solution 3:
It's actually best just to use Apple's implementation, as provided in the docs. However, the code they provide is faulty. Replace the portion found in keyboardWasShown:
just below the comments to the following:
NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);
CGPoint origin = activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
[backScrollView setContentOffset:scrollPoint animated:YES];
}
The problems with Apple's code are these:
(1) They always calculate if the point is within the view's frame, but it's a ScrollView
, so it may already have scrolled and you need to account for that offset:
origin.y -= scrollView.contentOffset.y
(2) They shift the contentOffset by the height of the keyboard, but we want the opposite (we want to shift the contentOffset
by the height that is visible on the screen, not what isn't):
activeField.frame.origin.y-(aRect.size.height)
Solution 4:
In textFieldDidBeginEditting
and in textFieldDidEndEditing
call the function [self animateTextField:textField up:YES]
like so:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField:textField up:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField:textField up:NO];
}
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
const int movementDistance = -130; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: @"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
I hope this code will help you.
Swift 5
func animateTextField(textField: UITextField, up: Bool) {
let movementDistance:CGFloat = -130
let movementDuration: Double = 0.3
var movement:CGFloat = 0
if up {
movement = movementDistance
} else {
movement = -movementDistance
}
UIView.animate(withDuration: movementDuration, delay: 0, options: [.beginFromCurrentState], animations: {
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
}, completion: nil)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
animateTextField(textField: textField, up:true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
animateTextField(textField: textField, up:false)
}
Solution 5:
Just using TextFields:
1a) Using Interface Builder
: Select All TextFields => Edit => Embed In => ScrollView
1b) Manually embed TextFields in UIScrollView called scrollView
2) Set UITextFieldDelegate
3) Set each textField.delegate = self;
(or make connections in Interface Builder
)
4) Copy / Paste:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
[scrollView setContentOffset:scrollPoint animated:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
[scrollView setContentOffset:CGPointZero animated:YES];
}