Blog

A drop-in universal solution for moving text fields out of the way of the keyboard

There are a hundred and one proposed solutions out there for how to move UITextField and UITextView out of the way of the keyboard during editing — usually, it comes down to observing UIKeyboardWillShowNotification and UIKeyboardWillHideNotification, or implementing UITextFieldDelegate delegate methods, and adjusting the frame of the superview, or using UITableView‘s scrollToRowAtIndexPath:atScrollPosition:animated:, but all the proposed solutions I’ve found tend to be quite DIY, and have to be implemented for each view controller that needs it.

I thought I’d put together a relatively universal, drop-in solution: UIScrollView and UITableView subclasses that handle everything.

When the keyboard is about to appear, the subclass will find the subview that’s about to be edited, and adjust its frame and content offset to make sure that view is visible, with an animation to match the keyboard pop-up. When the keyboard disappears, it restores its prior size.

It should work with basically any setup, either a UITableView-based interface, or one consisting of views placed manually.

201104121152.jpgFor non-UITableViewControllers, use it as-is by dropping the TPKeyboardAvoidingScrollView source files into your project, popping a UIScrollView into your view controller’s xib, setting the class to TPKeyboardAvoidingScrollView, and putting all your controls within that scroll view.

To use it with UITableViewController, pop the TPKeyboardAvoidingTableView source files in, and just make your UITableView a TPKeyboardAvoidingTableView in the xib — everything should be taken care of.

You can grab the source files, which includes a sample project, over on the GitHub project page

Tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

99 Comments

  1. Benjamin
    Posted February 16, 2012 at 11:51 pm | Permalink

    Michael, this is a really usefull class, however, I discovered unusual behavior when using your class on an iPad. It seems to extend the scroll view too much and thus, creates unusual behavior when selecting a textfield, as the scroll view will scroll up even though it is not necessary (i.e. when the keyboard does not cover the text field). Is there a workaround for this?

    Also, is it possible to have the view containing the textfields to reposition itself to the position it was before the keyboard appears?

    • Benjamin
      Posted February 17, 2012 at 12:07 am | Permalink

      EDIT: I wanted to add to my comment, that the iPad orientation seems to have something to do. I have my xib file set to portrait mode, when running the App on the iPad and turning it to landscape mode, the size of the scroll view seems to be the original height of the portrait view, and not the height of the landscape view, which is what we want, right?.

  2. Posted February 17, 2012 at 2:18 am | Permalink

    Benjamin,

    To handle orientation changes properly, you need to set the “Autosizing” properties of the UIScrollView/TPKeyboardAvoidingScrollView.

    I’ve put a screenshot here.

    • Benjamin
      Posted February 17, 2012 at 3:02 am | Permalink

      Hi, thanks for the response. My scrollview is already configured that way, I have attached a screenshot showing my problem. Notice the scroll Indicator, It shows extra space on the bottom, but it’s not supposed to do that :( The content shown on the picture is all the content I have made for that view

      http://imageshack.us/photo/my-images/543/screenshot20120216at859.png/

  3. Tim
    Posted March 6, 2012 at 12:59 pm | Permalink

    Fantastic class. Been a great time saver, so easy to utilise. And saves me bloating each of my classes with countless delegate methods!

  4. Gerson Noboa
    Posted March 7, 2012 at 6:26 pm | Permalink

    This is truly amazing, I’ve been trying different methods with no success, especially on TextViews. Thank you so much!

  5. Posted March 19, 2012 at 6:57 am | Permalink

    Thanks for this! This was extremely useful! :)

  6. Michael J Albanese
    Posted March 30, 2012 at 8:12 pm | Permalink

    First off thanks for the enlightening clarity on this often discussed (and often confusing) topic. I find your classes very useable. One thing though, I too experienced some jumping/jerky animation of the keyboard at times, along with the scroll view landing (what appeared to be) back where it started.

    I found a race condition between when the NSNotifications are delivered and when a textField (or textView) didBeginEditing method got invoked by the system. Recall it is these methods our apps are to then call into the TP class’s [scrollView adjustOffsetToIdealIfNeeded];

    The problem looks to be setting of the _keyboardVisible boolean too early, as in when the nsnotification arrives and prior to the initial keyboard scroll animation visually completing.

    The fix I came up with is reworking the animation inside the notification to use blocks, and also supply a completion handler. It is only in the completion handler where I then set _keyboardVisible = YES. Waiting for the initial scroll to complete before setting the flag, as opposed to before, allows the other code looking at _keyboardVisible to behave more accurately.

    Steps to repro: – have a xib/viewcontroller with a few TextFields and a TextView near the bottom. – click in the lowest TextView, causing the initial appearance of keyboard – scrolling jumps/jerks

    xCode 4.2, running iPhone simulator 5.0

    Michael

    Hopefully I’ve conformed to the submission process correctly, as I’ve enclosed the animation code for the notification method keyboardWillShow below.

    ` float animDur = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; self.contentInset = [self contentInsetForKeyboard]; CGPoint calcPoint = CGPointMake(self.contentOffset.x, [self idealOffsetForView:firstResponder withSpace:[self keyboardRect].origin.y - self.bounds.origin.y]);

    [UIView animateWithDuration:animDur 
                     animations:^{
                         self.contentOffset = calcPoint;
                     } 
                     completion:^(BOOL finished) {
                         _keyboardVisible = YES;
                     }];
    

    `

    • Posted March 30, 2012 at 10:51 pm | Permalink

      Brilliant work, Michael, thanks so much – I love it when a solution is provided ;-) I’ll check this out tomorrow and add it to the github project.

      • Andres Garcia
        Posted June 28, 2012 at 6:55 pm | Permalink

        Has this jumpiness been fixed in the latest code on Github? I downloaded it on June 28 and it is still behaves like that.

        It would be great to get a fix, and that should make this a fantastic tool in every iOS developer’s toolkit.

        Thanks.

        • Andres Garcia
          Posted June 28, 2012 at 7:03 pm | Permalink

          ACtually, nevermind, the issue only happens in the iPhone Simulator when I hit the tab key. This is not a real issue with the device.

          Great job and thanks for sharing it with the community.

          A

  7. Ryan
    Posted April 12, 2012 at 12:24 am | Permalink

    It doesn’t work for me….Unknown class TPKeyboardAvoidingScrollView in Interface Builder file?

    • Luigi
      Posted April 14, 2012 at 11:30 pm | Permalink

      Hi, I have the same problem. Have you solved? Thanks

      • jasonbub
        Posted April 17, 2012 at 1:59 am | Permalink

        I think that I am having the same problem. I grabbed the code last night.

        Here is the exact error: 2012-04-16 17:54:01.490 Moto Log[24021:1a303] Unknown class TPKeyboardAvoidingScrollView in Interface Builder file. 2012-04-16 17:54:01.493 Moto Log[24021:1a303] 0 2012-04-16 17:54:01.493 Moto Log[24021:1a303] 1 2012-04-16 17:54:01.494 Moto Log[24021:1a303] 2 2012-04-16 17:54:03.367 Moto Log[24021:1a303] -[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0x8eef060 2012-04-16 17:54:03.367 Moto Log[24021:1a303] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0x8eef060′

      • Herz
        Posted April 23, 2012 at 4:16 pm | Permalink

        Make sure that when you add the .h and .m files, the “Add to targets” checkbox is marked, that solved my problem.

  8. Corvalis
    Posted May 29, 2012 at 7:36 pm | Permalink

    Great job. Simple and elegant. Perfect.

  9. Miwi
    Posted May 31, 2012 at 12:23 pm | Permalink

    Great Job!!! Useful and very well done!! Thank a lot for sharing!!

  10. RNB-IT
    Posted June 7, 2012 at 8:56 pm | Permalink

    What to do when i use a custom cell, the cell is 80% of the size off the screen, the top textfield (active field) is scrolled off screen. but needs to be visible, and i want to change the TPkeyboardAvoidingTableView to be an UITableViewController, so i can add some of the managed objects to the class.

  11. Guillermo
    Posted June 28, 2012 at 4:30 am | Permalink

    Great solution but doesnt work with the ipad on landscape mode! please help!

  12. TedC
    Posted July 5, 2012 at 4:25 pm | Permalink

    This simply doesn’t work for me. I’ve been struggling with it: First, the UITextFieldDelegate methods weren’t called, but I realized I needed to connect each text field’s delegate to File’s Owner in IB. The methods are now called.

    However, now when adjustOffsetToIdeaIfNeeded is called I get an Invalid Argument crash: -[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0xe879030 2012-07-05 11:10:30.348 WizardTest[25765:15203] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIScrollView adjustOffsetToIdealIfNeeded]: unrecognized selector sent to instance 0xe879030′

    This seems to point to trying to call this method on UIScrollView instead of TPKeyboardAvoidScrollView, but I’ve set the scroll view type to TPKeyboard… in my view class, and set the scroll view’s class to TPKeyboard… in IB (Custom Class). Everything else looks pretty much the same in my app compared to the sample app, except that my scroll view is created in IB but manually added to the view with addSubView. (Changing it to add it in IB doesn’t help, though.)

    Any help would be appreciated.

    • Hiram Elguezabal
      Posted January 30, 2013 at 6:58 pm | Permalink

      Excuse me but i think i have exactly your problem, the scrollView control thinks that it have to call a method in UIScrollView instead TPKeyboardAvoidScrollView, i think the problem is that IB dosent change the class even if you put it manually in the outlet creation dialog box. Did you get the solution? Thanks a lot.

      • Hiram Elguezabal
        Posted January 30, 2013 at 10:53 pm | Permalink

        Im going to answer my self because i found the problem, when the outlet is created you have to give it the type in this case TPKeyboardAvoidScrollView, the step that i missing is that i have to select the scrollView and in the identity inspector in the custom class field also have to be filled with TPKeyboardAvoidScrollView. Now it works just fine!

        Tnx to Michael Tyson by the way!

  13. M J
    Posted July 22, 2012 at 2:52 pm | Permalink

    Dude, this library is sick! So easy to use–well done.

  14. Alvin
    Posted July 30, 2012 at 10:32 am | Permalink

    receiving error like this Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key viewScroll. what is that about? thanks anyway! :)

  15. Behzad
    Posted August 20, 2012 at 5:24 pm | Permalink

    First of all, I found this post really awesome. There is only one minor issue that I can see when I enable “Correction” on the UITextView. Once typing begins, it shows some odd flicker of pixels on the top of the keyboard. Is there something I can do to avoid this problem other than disabling “Correction” ?

  16. Ken Walker
    Posted September 1, 2012 at 6:03 pm | Permalink

    Michael, Thanks for this terrific class. I’m using in an IOS 5.1 project using Storyboards and ARC. The library works just fine but with two small differences from the sample project. I don’t get the “Next” or “Done” button when moving thru the fields in the Scrollview. I’m using the TPKeyboardAvoidingScrollView class. I’m probably missing something really trivial. Any suggestions?

    Thanks again so much for this class! Ken

    • Ken Walker
      Posted September 1, 2012 at 6:30 pm | Permalink

      Like I said it was trivial. I had forgotten to change the appropriate property value in IB. Sorry to bug you.

      • Duane
        Posted August 12, 2013 at 4:37 pm | Permalink

        I’m having the same problem … can’t seem to figure out this trivial issue.

        • Duane
          Posted August 12, 2013 at 4:50 pm | Permalink

          Nevermind … you’re right. Very trivial. Just had to select the correct delegate. Opps!

  17. Chad Zeluff
    Posted October 31, 2012 at 7:41 pm | Permalink

    Having an issue. Not using IB to build anything, just code. Here’s my block to screate the scrollview:

    TPKeyboardAvoidingScrollView *screen = [[TPKeyboardAvoidingScrollView alloc] initWithFrame:rect]; screen.backgroundColor = [UIColor clearColor]; screen.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; screen.autoresizesSubviews = YES; screen.showsHorizontalScrollIndicator = NO;

    Then I add the TextField as a subview:

    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(40.0f, 431.0f, 460.0f, 45.0f)]; textField.delegate = self; [textField setPlaceholder:@”Email Address”]; textField.returnKeyType = UIReturnKeyDone; [screen addSubview:textField];

    My View Controller is registered as a UITextFieldDelegate, and the following function IS being called when I click ‘Done’ on the keyboard:

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

    I have also verified that this textField IS the firstResponder at the time of the ‘Done’ click, so I’m definitely resigning the textField from a firstResponder state.

    This is on an iPad-only app. Thank you so much for any help.

    • Chad Zeluff
      Posted October 31, 2012 at 7:43 pm | Permalink

      Apologies. Forgot to mention what the problem was, haha. The keyboard isn’t being dismissed, even though it should since resignFirstResponder is being called.

      • Chad Zeluff
        Posted October 31, 2012 at 7:52 pm | Permalink

        Apologies. This is NOT a TPKeyboard… issue. Put the following into your View Controller if the View Controller is presented modally.

        • (BOOL)disablesAutomaticKeyboardDismissal { return NO; }
  18. Posted November 30, 2012 at 2:54 pm | Permalink

    Thank you. Simply works and very straight forward to implement.

  19. Aaron Nelson
    Posted November 30, 2012 at 9:01 pm | Permalink

    Thank you so much! It’s nice to see people that know how to make self contained code. You definitely save me some headaches.

  20. Posted December 1, 2012 at 4:38 am | Permalink

    Thanks a lot for this, really well coded and simple to use.

  21. Posted December 24, 2012 at 5:07 pm | Permalink

    Amazing. There is only one minor issue that I can see when I enable “Correction” on the UITextView.

  22. Oskmeister
    Posted January 14, 2013 at 12:16 pm | Permalink

    Thanks for the great script. Saved me an hour or five :-)

  23. Andres Garcia
    Posted January 25, 2013 at 12:20 am | Permalink

    I am experiencing an issue with iOS 6 and AutoLayouts. I see this error:

    *** Assertion failure in -[TPKeyboardAvoidingTableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2372/UIView.m:5776

    Has anyone else seen this? I don’t want to not use Auto-Layout to be the solution. I hope this can be fixed. I would really appreciate it.

  24. Dannie
    Posted April 16, 2013 at 7:32 pm | Permalink

    the situation: you have a scroll view with 2 text fields the issue: 1. click the second text field (keyboard goes up) 2. click the first text field (keyboard goes back) 3. click on the scroll view (keyboard dismisses); but the content offset is skewed

    I’m not sure if this is the right solution, but commenting the following line in the method -(void)scrollToActiveTextField solves the issue.

    line _originalContentOffset = self.contentOffset;

    P.S. my original insets are 0. reproduces in ios 5 and 6.

    Please let me know if there are any better way to fix the issue.

  25. George
    Posted May 9, 2013 at 2:39 pm | Permalink

    Very nice solution and elegant. Thanks

  26. Posted June 21, 2013 at 12:56 am | Permalink

    Thanks for this solution! Of the 5-6 I found, yours worked the best and easiest.

  27. Ricardo
    Posted October 5, 2013 at 3:29 am | Permalink

    Hi Michael. Thank you for sharing this. It’s a real lifesaver class.

    I’ve noticed that the TPKeyboardAvoiding view doesn’t scroll until I select a textview and tap to close the keyboard. I can’t scroll the view before. Is this normal?. Can I force the view to act like a regular scrollview at loading?

    Thank you

    • Posted October 7, 2013 at 7:20 am | Permalink

      Hi Ricardo – actually, this is ordinary UIScrollView behaviour; you need to set the content size appropriately. TPKeyboardAvoiding includes a utility method that can do this for you, contentSizeToFit, but you need to call it yourself.

      • Ricardo
        Posted October 7, 2013 at 10:12 am | Permalink

        Ok. Thank you Michael. In fact, I was using it wrong. Now it works. :D

  28. Mangesh Vyas
    Posted December 7, 2013 at 4:00 pm | Permalink

    I have long scroll view with 12 tetxFiled on it. It scroll for few textfiled but after that it doesn’t scroll. I have set Content size manually and also call your function contentSizeToFit but nothing happen. I am using iOS 7. Can you help me for the same.