38

I know that this question has been asked over and over again, but nothing seems to be working for me. Most of the solutions around are pretty out of date, and the rest are incredibly huge blocks of code that are ten times larger then the actual projects coding. I have a few UITextFields lined up vertically, but when the keyboard launches to edit one, it covers up the text field. I was wondering if there is a simple beginner way to scroll the view up, and then back down when the editing starts and ends?

Thank you.

Henry F
  • 4,868
  • 10
  • 48
  • 96
  • possible duplicate of [How to make a UITextField move up when keyboard is present](http://stackoverflow.com/questions/1126726/how-to-make-a-uitextfield-move-up-when-keyboard-is-present) – David Z Jan 03 '12 at 01:51

7 Answers7

29

I have made solutions that work with scroll and non-scroll views using keyboard notification and a detection of the current first responder, but sometimes I use this trivial solution instead: The simple way is to detect the opening keyboard via the text field delegate's textViewDidBeginEditing: method and to move the entire view up. The easiest way to do this is with something along the lines of changing self.view.bounds.origin.y to -100 (or whatever). Use the corresponding textViewShouldEndEditing: method to set it to the opposite, which is 100 in this case. Changing bounds is a relative procedure. After changing it the frame is moved but the bounds origin is still zero.

Peter DeWeese
  • 17,664
  • 8
  • 75
  • 98
  • 6
    Thank you. This is my code so far: `-(BOOL)textViewDidBeginEditing:(UITextField *)textField { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.35f]; CGRect frame = self.view.frame; frame.origin.y = -100; [self.view setFrame:frame]; [UIView commitAnimations]; }` But I'm getting a warning that says "Control reaches end of non-void function". I'm not sure how to get around this, sorry I'm still a beginner at this. – Henry F Jan 02 '12 at 23:15
  • 1
    np. Add `return YES;` at the end of the method to say that you are allowing the editing. Don't forget to set this class as the delegate for that text view. There is a corresponding method for UITextFieldDelegates called `textFieldDidBeginEditing:` if you need. – Peter DeWeese Jan 03 '12 at 00:10
  • Awesome, that did the trick, thanks a lot. Just one more question though, how do I set this class as the delegate for that text view? Sorry if its a dumb question lol. – Henry F Jan 03 '12 at 00:31
  • You can do it in the xib/storyboard by right clicking on the text view and dragging from delegate to your file owner, or you can do it programmatically in that class in `viewDidLoad` after the text view is instantiated like `myTextView.delegate = self`. – Peter DeWeese Jan 03 '12 at 01:06
  • I met the problem as you guys and did as what you did. However, the `-textViewDidBeginEditing` and `-textFieldShouldEndEditing` are never called. Why? – Ben Lu Feb 23 '12 at 08:55
  • Also addition: when you hide keybord , you can do this . - (BOOL)textFieldShouldEndEditing:(UITextField *)textField { [UIView setAnimationDuration:0.35f]; CGRect frame = self.view.frame; frame.origin.y = 0; [self.view setFrame:frame]; [UIView commitAnimations]; } – Erhan Demirci Aug 01 '13 at 14:15
25

Since I found it, I use TPKeyboardAvoiding - https://github.com/michaeltyson/TPKeyboardAvoiding.

It is working great, and is very easy to setup:

  • Add a UIScrollView into your view controller's xib
  • Set the scroll view's class to TPKeyboardAvoidingScrollView (still in the xib, via the identity inspector)
  • Place all your controls within that scroll view

You can also create it programmatically, if you want.


There is a class for the same need inside a UITableViewController ; it is only needed in case you support a version of iOS below 4.3.

Guillaume
  • 21,205
  • 6
  • 60
  • 95
  • Cool, thank you, I'm going to try this now. This might be a dumb question, but I already set up the nib, will adding a scroll view on top of the nib mess it up at all? (I also have set a background in Image View) – Henry F Jan 02 '12 at 23:18
  • Just add the view to the nib, then move your other elements inside the view. All your elements properties (IBOutlet, configuration) will be kept. In case you messed up, cancel the last step (or revert to your latest commit!) – Guillaume Jan 02 '12 at 23:26
  • if the library is done by Micheal Tyson, then I'm using it. Simple. – abbood Aug 12 '13 at 08:10
  • If you're like me and you have custom input views and input accessory views, this is the simplest solution. – michaelsnowden Jul 20 '14 at 18:34
  • This library is now working in case my last text view is growing, that time my text view is going down side of keyboard. – Ganesh Pawar Nov 19 '19 at 14:06
3

@BenLu and other users who are facing problem of the function are never getting called is because of following reason: As the delegate inbuild function bydefaults return void instead of BOOL this is how it should be as follows:

 -(void)textFieldDidBeginEditing:(UITextField *)textField
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.35f];
    CGRect frame = self.view.frame;
    frame.origin.y = -100;
    [self.view setFrame:frame];
    [UIView commitAnimations];
}

-(void)textFieldDidEndEditing:(UITextField *)textField
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.35f];
    CGRect frame = self.view.frame;
    frame.origin.y = 100;
    [self.view setFrame:frame];
    [UIView commitAnimations];
}
1

I spent sometime on this problem and gathered pieces code to create one final solution. My problem was related to UITableView scrolling and keyboard open/close.

You need two partial methods in your cell class:

    void EditingBegin(UITextField sender)
    {
        // Height of tallest cell, you can ignore this!
        float tableMargin = 70.0f;
        float tableHeight = _tableView.Frame.Size.Height;
        float keyBoardHeight = KeyboardHeight();

        NSIndexPath[] paths = this._tableView.IndexPathsForVisibleRows;
        RectangleF rectLast = this._tableView.RectForSection(paths[paths.Length - 1].Section);
        RectangleF rectFirst = this._tableView.RectForSection(paths[0].Section);
        float lastCellY = rectLast.Y - rectFirst.Y;
        if (lastCellY > tableHeight - keyBoardHeight)
        {
            float diff = lastCellY - (tableHeight - tableMargin - keyBoardHeight);
            this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, diff, 0.0f);
        }

        float cellPosition = this._tableView.RectForSection(this._section).Y;
        if (cellPosition > tableHeight - keyBoardHeight)
        {
            if (this._tableView.ContentInset.Bottom == 0.0f)
            {
                float diff = cellPosition - (tableHeight - tableMargin - keyBoardHeight);
                this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, diff, 0.0f);
            }
            else
            {
                this._tableView.ScrollToRow(NSIndexPath.FromItemSection(0, this._section), UITableViewScrollPosition.Middle, true);
            }
        }
    }

    partial void EditingEnd(UITextField sender)
    {
        UIView.BeginAnimations(null);
        UIView.SetAnimationDuration(0.3f);
        this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, 0.0f, 0.0f);
        UIView.CommitAnimations();
    }

and then in your view controller class:

    public override void WillAnimateRotation(UIInterfaceOrientation toInterfaceOrientation, double duration)
    {
        base.WillAnimateRotation(toInterfaceOrientation, duration);

        float bottom = this.TableView.ContentInset.Bottom;
        if (bottom > 0.0f)
        {
            if (toInterfaceOrientation == UIInterfaceOrientation.Portrait || toInterfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown)
            {
                bottom = bottom * UIScreen.MainScreen.Bounds.Width / UIScreen.MainScreen.Bounds.Height;
            }
            else
            {
                bottom = bottom * UIScreen.MainScreen.Bounds.Height / UIScreen.MainScreen.Bounds.Width;
            }

            UIEdgeInsets insets = this.TableView.ContentInset;
            this.TableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, bottom, 0.0f);
        }
    }  
U. Ali
  • 151
  • 2
  • 3
  • 7
0

If you have a UITableView or a UIScrollView it's better to change values for contentOffset instead of making changes to the frame.

Working on Peter's Answer, adding this method to your class works nicely:

- (void)textViewDidBeginEditing:(UITextField *)textField {
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.35f];
    CGPoint offset = self.tableView.contentOffset;
    offset.y += 200; // You can change this, but 200 doesn't create any problems
    [self.tableView setContentOffset:offset];
    [UIView commitAnimations];
}

That's it, no need to add the textViewDidEndEditing method.


I shouldn't need to say this, but for this to work your UITextField or UITextView must be a delegate of your controller.

Community
  • 1
  • 1
Sheharyar
  • 65,583
  • 19
  • 152
  • 196
0

Starting with Peter's answer, I developed the following approach in Swift 3.0 under iOS 10.1. I'm doing this for a textView, so I have implemented the UITextViewDelegate functions textViewDidBeginEditing and textViewDidEndEditing where I adjust the view's bounds. As you can see, I set the origin Y value to a small positive number to scroll up and then back to 0 to return to the original position.

Here is the relevant code from my ViewController. You don't need to animate, but it adds a nice touch.

func textViewDidBeginEditing(_ textView: UITextView)
{
    if UIScreen.main.bounds.height < 568 { 
        UIView.animate(withDuration: 0.75, animations: {
            self.view.bounds.origin.y = 60
        })
    }
}

func textViewDidEndEditing(_ textView: UITextView)
{
    if UIScreen.main.bounds.height < 568 {
        UIView.animate(withDuration: 0.75, animations: {
            self.view.bounds.origin.y = 0
        })
    }
}
Mario Hendricks
  • 607
  • 8
  • 6
-1

i have a scrollview and 3 text fields in this. I have a simple code from my own application :

.h file is :

#import <UIKit/UIKit.h>

@interface AddContactViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>

@property (nonatomic, retain) NSDictionary *dict_contactDetail;

@property (nonatomic, retain) IBOutlet UILabel *lbl_name;
@property (nonatomic, retain) IBOutlet UITextField *txtField_tel;
@property (nonatomic, retain) IBOutlet UITextField *txtField_address;
@property (nonatomic, retain) IBOutlet UITextField *txtField_email;

@property (nonatomic, retain) IBOutlet UIScrollView *scrollView;

@end

.m file :

#import "AddContactViewController.h"

@interface AddContactViewController ()

@end

@implementation AddContactViewController

@synthesize dict_contactDetail;

@synthesize lbl_name, txtField_tel, txtField_email, txtField_address, scrollView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil       
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

   // NSLog(@"dict_contactDetail : %@", dict_contactDetail);

    UIBarButtonItem * rightButton = [[UIBarButtonItem alloc] initWithTitle:@"Add" style:UIBarButtonSystemItemDone target:self action:@selector(addEmergencyContact:)];
    self.navigationItem.rightBarButtonItem = rightButton;


    lbl_name.text = [NSString stringWithFormat:@"%@ %@", [dict_contactDetail  valueForKey:@"fname"], [dict_contactDetail  valueForKey:@"lname"]];

    txtField_tel.returnKeyType = UIReturnKeyDone;
    txtField_email.returnKeyType = UIReturnKeyDone;
    txtField_address.returnKeyType = UIReturnKeyDone;

}



-(void)addEmergencyContact:(id)sender
{
    scrollView.frame = CGRectMake(0, 0, 320, 460);
}

#pragma mark - text field delegates
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    if([textField isEqual:txtField_tel])
    {
        [scrollView setContentOffset:CGPointMake(0, 70)];
        scrollView.frame = CGRectMake(0, 0, 320, 210);
    }
    if([textField isEqual:txtField_address])
    {
        [scrollView setContentOffset:CGPointMake(0, 140)];
        scrollView.frame = CGRectMake(0, 0, 320, 210);
    }
    if([textField isEqual:txtField_email])
    {
        [scrollView setContentOffset:CGPointMake(0, 210)];
        scrollView.frame = CGRectMake(0, 0, 320, 210);
    }
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    scrollView.frame = CGRectMake(0, 0, 320, 460);
    [textField resignFirstResponder];
    return YES;
}



@end
YogiAR
  • 2,059
  • 19
  • 43