Adjust UITextField hidden behind Keyboard with UIScrollView

Every time you create a form you’re gonna probably have some UITextField in it. When you have a lot of them, it’s very commom to have the UITextField hidden behind the Keyboard. I’ve searched all over for a solution to the problem of textfields hidden behind the keyboard and found that the solution is to use a UIScrollView. It’s possible to scroll some items over the keyboard and animate them so it goes up as smooth as Apple’s own applications

The first thing worth of note that I could find was a blog post by John Muchow in which he describes a possible solution. According to his text you can solve the problem by implementing UIKeyboardDidShowNotification and UIKeyboardDidHideNotification. These are the main notification events a keyboard will fire. There is also UIKeyboardWillHideNotification that can be used for our purpose.

The problem with his solution is that the field only pops when you actually start typing, not when you touch/click the UITextField. This one was kinda easy to solve, just using scrollRectToVisible, but it still put the UITextField right over the keyboard. As I wanted to mimic as much as Apple Design Guidelines for Moving Content Under the Keyboard, I put a small offset on it. You can download the sample and read through the text to understand it.

Another great text you can found on Sliding UITextFields, where Matt Gallagher describes an approach that splits the screen in three sections.

Creating Base App

From XCode menu choose New Project, IPhone OS Application and finally View Base Application. Let’s name it sample-scrollview. Under Classes, there should be a file named sample_scrollviewViewController.h, open it and create three IBOutlets we’re gonna need to demonstrate this sample.

// sample_scrollviewViewController.h
@interface sample_scrollviewViewController : UIViewController {
    IBOutlet UIScrollView *scrollview;
    IBOutlet UITextField *tx1;
    IBOutlet UITextField *tx2;
}

@property(nonatomic,retain) IBOutlet UIScrollView *scrollview;
@property(nonatomic,retain) IBOutlet UITextField *tx1;
@property(nonatomic,retain) IBOutlet UITextField *tx2;

And then add the synthesize directive in the implementation file:

// sample_scrollviewViewController.m
@implementation sample_scrollviewViewController

@synthesize scrollview,tx1,tx2;

Create Interface Using Interface Builder

Next we’re gonna create the User Interface using XCode’s Interface Builder. Browse the project looking for sample_scrollviewViewController.xib inside the Resources folder.File's Owner Properties

Double-click sample_scrollviewViewController.xib to open interface builder. From the Object Library drag a Scroll View (UIScrollView) into the main view, it should resize appropriately when you drop it over the existing view. Now drag two UITextFields over the UIScrollView. Now it’s time to connect the pieces on the Interface Builder to the IBOutlets inside your viewcontroller. Now right-click the File’s Owner Icon and associate the IBOutlets. You should associate the scrollview with the UIScrollView object, and tx1 and tx2 with the respective UITextFields on the screen. Then you should drag the Referencing Outlets to the TextFields and choose the delegate. Now you may close Interface Builder as we’re not gonna need it again for now. There is a good explanation about doing this on Interface Builder on Jonathan Crowe’s Animating a View in IPhone SDK or if you want to do it programatically use How can I add a UIScrollView to my Application

Implementing Notifications for Keyboard Activity

Now open up again the implementation file and let’s code what we need to receive notifications from keyboard activity. First of all let’s define some constants that we’re gonna need to determine the size of the content. It’s important to note, that if you define a content height taller than the iphone screen you’ll and up with scrolling on the screen when the keyboard is hidden. Our objective is to keep the scroll of when the keyboard is hidden. Next to the top of the implementation file, right before the import statement, let’s define the following:

#define SCROLLVIEW_CONTENT_HEIGHT 460
#define SCROLLVIEW_CONTENT_WIDTH  320

#import "sample_scrollviewViewController.h"

Then we need to implement the methods to listen to the notification of keyboard activity. But first, let’s get back to the header file and create a variable to keep state information about the visibility of the keyboard. You can put it right after the declaration of the IBOutlets.

	IBOutlet UITextField *tx2;

	BOOL keyboardVisible;

There were a couple of questions on stackoverflow about doing this. One of them is an answer by Micheal Baltaks where he explains how to handle this when you’re using tableviews that need to be scrolled, another one is How to Make a UITextField move up when keyboard is present.
Then let’s implement code to handle notifications from keyboard. These methods will allow us to receive the notifications sent from the events.

- (void) viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	NSLog(@"Registering for keyboard events");

	// Register for the events
	[[NSNotificationCenter defaultCenter]
				addObserver:self
				selector:@selector (keyboardDidShow:)
				name: UIKeyboardDidShowNotification
				object:nil];
	[[NSNotificationCenter defaultCenter]
				addObserver:self
				selector:@selector (keyboardDidHide:)
				name: UIKeyboardDidHideNotification
				object:nil];

	// Setup content size
	scrollview.contentSize = CGSizeMake(SCROLLVIEW_CONTENT_WIDTH,
										SCROLLVIEW_CONTENT_HEIGHT);

	//Initially the keyboard is hidden
	keyboardVisible = NO;
}

-(void) viewWillDisappear:(BOOL)animated {
	NSLog (@"Unregister for keyboard events");
	[[NSNotificationCenter defaultCenter]
				removeObserver:self];
}

Then implement the code to handle these notifications, but first we need to create another state variable, this one, offset, will hold the distance of the content from the top of the scroll, we are gonna need it to restore the scroller to its original position. Just put it right after the keyboardVisible.

	BOOL keyboardVisible;
	CGPoint	offset;

Now the code for the keyboardDidShow, put it in the implementation file.

-(void) keyboardDidShow: (NSNotification *)notif {
	NSLog(@"Keyboard is visible");
	// If keyboard is visible, return
	if (keyboardVisible) {
		NSLog(@"Keyboard is already visible. Ignore notification.");
		return;
	}

	// Get the size of the keyboard.
	NSDictionary* info = [notif userInfo];
	NSValue* aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
	CGSize keyboardSize = [aValue CGRectValue].size;

	// Save the current location so we can restore
	// when keyboard is dismissed
	offset = scrollview.contentOffset;

	// Resize the scroll view to make room for the keyboard
	CGRect viewFrame = scrollview.frame;
	viewFrame.size.height -= keyboardSize.height;
	scrollview.frame = viewFrame;

	CGRect textFieldRect = [tx1 frame];
	textFieldRect.origin.y += 10;
	[scrollview scrollRectToVisible:textFieldRect animated:YES];

	NSLog(@"ao fim");
	// Keyboard is now visible
	keyboardVisible = YES;
}

At this point, if you run the application, you can see that the textfield you clicked (tx1) moved over the keyboard. Explaining a little bit the code above, on line 4, we check if the keyboard is already visible, it may happen if for example you click in another TextField. Later, on lines 9-12 we get the size of the keyboard. On line 16 we save the original position (offset) of the content frame, so as to restore it later when the keyboard hides. On lines 18-21 we resize the UIScrollView frame, subtracting the size of the keyboard. Lines 23-25 get the position of the first UITextField and tell the UIScrollView to scroll until it’s visible, before, on line 24 we add 10 points as a margin from the UITextField to the Keyboard. At last on line 29 we configure the keyboard state variable as visible.

Implementing the Keyboard Handling Events

-(void) keyboardDidHide: (NSNotification *)notif {
	// Is the keyboard already shown
	if (!keyboardVisible) {
		NSLog(@"Keyboard is already hidden. Ignore notification.");
		return;
	}

	// Reset the frame scroll view to its original value
	scrollview.frame = CGRectMake(0, 0, SCROLLVIEW_CONTENT_WIDTH, SCROLLVIEW_CONTENT_HEIGHT);

	// Reset the scrollview to previous location
	scrollview.contentOffset = offset;

	// Keyboard is no longer visible
	keyboardVisible = NO;

}

But again, if you run the code right now, you will notice that the keyboard does not close, and that you cannot see the same effect on the second UITextField. Now we’re gonna fix that. Just add another variable to hold the state of which textfield is active, put it in your header file, which at last will look like the following:

@interface sample_scrollviewViewController : UIViewController {
    IBOutlet UIScrollView *scrollview;
    IBOutlet UITextField *tx1;
    IBOutlet UITextField *tx2;

	BOOL keyboardVisible;
	CGPoint offset;
	UITextField *activeField;
}

Making the Keyboard Close

Now we need to implement UITextField method delegates to make the keyboard close and set the active UITextField. The first code snippet, sets the current active UITextField:

-(BOOL) textFieldShouldBeginEditing:(UITextField*)textField {
	activeField = textField;
	return YES;
}

The second one, makes the keyboard close

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
	[textField resignFirstResponder];
	return YES;
}

Also we need to change the implementation method of keyboardDidShow, and change the line that recover the frame of the UITextField to the following:

	scrollview.frame = viewFrame;

	CGRect textFieldRect = [activeField frame];
	textFieldRect.origin.y += 10;

As you can see it’s very simple to achieve good results, if you want to go further, you should calculate the middle of the visible area, so as to have a broader margin from the keyboard, this will mimic even better Apple Behavior in their applications.

You can download the full XCode Project for Adjusting UITextFields hidden behind Keyboard or browse the code on github.

14 Responses to “Adjust UITextField hidden behind Keyboard with UIScrollView”

  1. Craig J. Remy on January 9th, 2010 at 01:40

    This is a very useful post. Thank you. One question, how do you animate the UIScrollView back into position. Currently it just snaps back which is not very elegant.

  2. Great article, this perfectly fixed a problem I was experiencing. Thanks!

  3. Craig, I even tried to come up with an animated version, but then I gave up. If you notice the behavior of apple’s own applications (like Notes for instance) just snaps back also. In my tests I used some basic animations like in this post but after all, it seems impossible to resize the UIScrollView before the keyboard hides, and then the UITextFields really appear cutted while it resizes.

  4. Hi. Great article. The link to the source code seem to be do different code.

    Can you recheck the link?

    thanks

    johndesp

  5. Bhavesh Patel on April 29th, 2010 at 06:16

    This is the simple way to do this

    - (void)setViewMovedUp:(BOOL)movedUp
    {
    	[UIView beginAnimations:nil context:NULL];
    	[UIView setAnimationDuration:0.3];
    	CGRect rect = self.view.frame;
    	rect.origin.y = 0.0;
    	
    	if (movedUp)
    	{
    		rect.origin.y -= kOFFSET_FOR_KEYBOARD;
    	}
    	
    	if(!movedUp){
    		
    		rect.origin.y = 0.0f;
    	}
    	
    	self.view.frame = rect;
    	[UIView commitAnimations];
    	
    }
    
  6. Hey friend,

    Thank you so much. your description was very helpful, it really helped me finding solution to my problem. Great article. Thanks a lot.

  7. @Johndesp I’ve double check the code and it’s the same as the text. Maybe you didn’t notice, that the code in the zipfile is the final version, with the changes that add the activefield attribute. Let me know if I can help you in any way.

    muanis

  8. @bhavesh very good point, i’ve found some more info on this approach in this discussion

  9. Ciao !

    Thank’s for this tutorial.
    I use your method and work very well, but i have a problem,
    I have 3 textfield in my screen, 2 at the bottom and hidden behind the keyboard, and 1 textfield in the upper side.
    When i edit the textfield in the upper side , the view scroll up.
    It’s possible to scroll the view ONLY if the textfield is hidden ( > of the keyboard height)

    Thank’s so much !

    BrunoVR

  10. Hi, great article!
    I too was having an issue with the transition in either way being a bit too harsh so Ive fixed it by animating both scrollRectToVisible and the scrollView frame resize. It’s now very smooth and seamless in both directions!

    - (void)keyboardWillHide:(NSNotification*)notification{

    NSDictionary* userInfo = [notification userInfo];

    NSValue* boundsValue = [userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey];
    CGSize keyboardSize = [boundsValue CGRectValue].size;

    CGRect viewFrame = self.scrollView.frame;

    viewFrame.size.height += (keyboardSize.height);
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    CGRect oldFrame=CGRectMake(viewOffset.x, viewOffset.y, viewFrame.size.width , viewFrame.size.height );
    [scrollView scrollRectToVisible:oldFrame animated:YES];

    keyboardIsShown = NO;
    }

    -(void)keyboardWillShow:(NSNotification*)notification{

    if (keyboardIsShown) {
    return;
    }

    NSDictionary* userInfo = [notification userInfo];

    // get the size of the keyboard
    NSValue* boundsValue = [userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey];
    CGSize keyboardSize = [boundsValue CGRectValue].size;

    viewOffset=scrollView.contentOffset;

    CGRect viewFrame = self.scrollView.frame; viewFrame.size.height -= (keyboardSize.height );

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    CGRect textFieldRect = [currentField frame];
    textFieldRect.origin.y += 10;
    [scrollView scrollRectToVisible:textFieldRect animated:YES];

    keyboardIsShown = YES;
    }

    Cheers!

  11. Hi Neil!

    (http://www.iphonesampleapps.com/2009/12/adjust-uitextfield-hidden-behind-keyboard-with-uiscrollview/#comment-23)

    Rock it ! The smooth scroll is works perfectly while appearing and disappearing keyboard. Another advantage of your code is not using the depreciated key UIKeyboardBoundsUserInfoKey form the original code.

    Thank you !

  12. i would like to thanks for this fabulous support by providing this code.i m a new developer of iPhone. I am having a problem in this code. I have done the same work in my application but when i go to the next text field by the key board tab button the scroll automatically to the appropriate place but when i click to the next text field by mouse window dint not scroll at that time it required one key from keyboard then it scroll the window….so please suggest me what can i do…… :-)

  13. Thanks a lot, after passing a few hours and banging my head I found you. Such a simple and most importantly point to point solution for beginners. again thanks a llot

  14. Hello author,

    Thanks for the tutorial, but your code only works with scrollviews which cover the entire window, ie. height = 460. Sometimes scrollviews are less high than that, and code needs to be added to set the new height properly. Instead of subtracting keyboard height, consider this pseudo code:
    scrollview.height = keyboard top (is relative to window) – scrollview top (converted relative to window)

    Btw the keyboard frame swaps width and height when in landscape orientation.

    Best regards

Leave a Reply