Skip to content

Instantly share code, notes, and snippets.

@dlo
Last active February 26, 2021 07:33
Show Gist options
  • Save dlo/8572874 to your computer and use it in GitHub Desktop.
Save dlo/8572874 to your computer and use it in GitHub Desktop.
How to adjust a view's height with Auto Layout when a keyboard appears or disappears in iOS 7.

This gist outlines how to resize a view when a keyboard appears using Auto Layout (there are a bunch of code samples out there that manually adjust the view's frame, but that's just so 2013). The method I outline below works universally on both iPhone and iPad, portrait and landscape, and is pretty darn simple.

Setting Up

The first thing to do is to define our containing view controller, the view, and the bottom constraint that we'll use to adjust its size.

Here's HeightAdjustingViewController.h. We don't need to expose any public properties, so it's pretty bare.

@interface HeightAdjustingViewController : UIViewController

@end

Now, in the class extension in our implementation file, we define a view and a bottomConstraint, which will adjust the bottom position of our view when the keyboard appears and disappears.

@property (nonatomic, strong) UIView *adjustingView;
@property (nonatomic, strong) NSLayoutConstraint *bottomConstraint;

We also define two methods to send the keyboard hide and show notifications to.

- (void)keyboardWillHide:(NSNotification *)sender;
- (void)keyboardDidShow:(NSNotification *)sender;

Defining the View

We can now move on to the implementation. In our viewDidLoad method, we'll set up the view, add observers for the hide and show notifications, and define our bottomConstraint.

- (void)viewDidLoad {
    [super viewDidLoad]

    self.adjustingView = [[UIView alloc] init];
    self.adjustingView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:self.adjustingView];

    NSDictionary *views = @{@"view": self.adjustingView,
                            @"top": self.topLayoutGuide };

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[top][view]" options:0 metrics:nil views:views]];

    self.bottomConstraint = [NSLayoutConstraint constraintWithItem:self.adjustingView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.bottomLayoutGuide attribute:NSLayoutAttributeTop multiplier:1 constant:0];
    [self.view addConstraint:self.bottomConstraint];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];;
}

Notification Handlers

Great, we've got everything set up. Our visual layout constraints pin the adjusting view to the topLayoutGuide, and the bottomConstraint pins the bottom of the view to the bottomLayoutGuide (which we'll soon adjust using the constant).

Now it's time to set up our keyboard notification handlers.

The UIKeyboardDidShowNotification sends an NSNotification with a userInfo dictionary containing a key that has the final CGRect of the keyboards position on the screen. We're going to take that rectangle,

CGRect frame = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];

convert it into the coordinate system of the current view (this is necessary in the situation where the view's view controller is being presented modally, such as in a form sheet on the iPad, and is offset from the main windows coordinate system, which the keyboard's frame is given to us in),

CGRect newFrame = [self.view convertRect:frame fromView:[[UIApplication sharedApplication] delegate].window];

and then adjust the bottom constraint by the starting position of the keyboard in the Y-axis as offset from the height of the current superview.

self.bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(self.view.frame);

I know, it's a mouthful. After updating the constraint's constant, we call layoutIfNeeded to re-layout our subviews.

Here's the completed keyboardDidShow::

- (void)keyboardDidShow:(NSNotification *)sender {
    CGRect frame = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect newFrame = [self.view convertRect:frame fromView:[[UIApplication sharedApplication] delegate].window];
    self.bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(self.view.frame);
    [self.view layoutIfNeeded];
}

Now, keyboardWillHide: is a bit simpler. We don't care what the keyboard frame is; we just want to put things back to where they were before (pin the adjustingView back to the bottom). We do this by just reassigning the bottomConstraint's constant back to 0.

- (void)keyboardWillHide:(NSNotification *)sender {
    self.bottomConstraint.constant = 0;
    [self.view layoutIfNeeded];
}

Wrapping Up

And that's it. You can browse the files in their entirety below. Of course, I didn't add a text field that would make the keyboard appear, but I'll leave that as an exercise for the reader. Enjoy!

File List

#import <UIKit/UIKit.h>
@interface HeightAdjustingViewController : UIViewController
@end
#import "HeightAdjustingViewController.h"
@interface HeightAdjustingViewController ()
@property (nonatomic, strong) UIView *adjustingView;
@property (nonatomic, strong) NSLayoutConstraint *bottomConstraint;
- (void)keyboardWillHide:(NSNotification *)sender;
- (void)keyboardDidShow:(NSNotification *)sender;
@end
@implementation HeightAdjustingViewController
- (void)viewDidLoad {
[super viewDidLoad]
self.adjustingView = [[UIView alloc] init];
self.adjustingView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.adjustingView];
NSDictionary *views = @{@"view": self.adjustingView,
@"top": self.topLayoutGuide };
[self.view addConstraint:[NSLayoutConstraint constraintsWithVisualFormat:@"[top][view]" options:0 metrics:nil views:views]];
self.bottomConstraint = [NSLayoutConstraint constraintWithItem:self.adjustingView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.bottomLayoutGuide attribute:NSLayoutAttributeTop multiplier:1 constant:0];
[self.view addConstraint:self.bottomConstraint];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];;
}
#pragma mark - Notification Handlers
- (void)keyboardDidShow:(NSNotification *)sender {
CGRect frame = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect newFrame = [self.view convertRect:frame fromView:[[UIApplication sharedApplication] delegate].window];
self.bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(self.view.frame);
[self.view layoutIfNeeded];
}
- (void)keyboardWillHide:(NSNotification *)sender {
self.bottomConstraint.constant = 0;
[self.view layoutIfNeeded];
}
@end
@danauerbach
Copy link

Hi Dan, I like the simple approach you have, but I am having trouble getting it to work. Essentially nothing happens when keyboard appears. I am not exactly clear on how to set up the view hierarchy. I have a (Storyboard) view controller, with two UITextFields. I would like the TFs to scroll up when keyboard appears over them. But should the TFs be in a subview of the main ViewController view? Given you add the adjustingView as a subview, perhaps this is what's need to make the constraint.constant changes have any affect?

Also, a couple of things I notice:

In viewDIdLoad: (line 25):
[self.view addConstraint:...] looks like it should be [self.view addConstraints:...]

in keyboardDidShow: (line 40):
The bottomConstraint.constant is always negative. This is not what I expected...

Any info/tips much appreciated!

Xcode 5.1, ios 7.1.

cheers,
dan

@dlo
Copy link
Author

dlo commented Mar 17, 2014

Hmm, I see. Your situation is a tiny bit different. What I would do in your case is put the two TFs in a UIScrollView, and attach the bottom constraint to that.

If you run into any more issues, post some more code and I'll see what tips I can send your way :)

PS You were right about that typo on line 25 :)

@dlo
Copy link
Author

dlo commented Mar 18, 2014

@danauerbach I just released a project that makes the entire process of doing this much easier. Check it out:

https://github.com/lionheart/LHSKeyboardAdjusting

@nicolas-miari
Copy link

You could animate the changes to the constraint constant, calling self.view.layoutIfNeeded() inside the animation block, after setting the constant.

@AndreyPShevtsov
Copy link

I think there's a mistake
self.bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(self.view.frame);
and should be vice versa
self.bottomConstraint.constant = CGRectGetHeight(self.view.frame) - newFrame.origin.y;

@Igotit
Copy link

Igotit commented Oct 21, 2019

@AndreyPShevtsov +1. It should be vice versa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment