5

This question has been asked a couple of times, but I wasn't really able to find an answer...

In iOS6 I used the following to resize an UITextView whenever the keyboard appeared. Under iOS7 behavior is not as it should be (in my case, it seems like nothing is resizing at all). I suspect the cause to be the auto-layout / constraint behavior of iOS7. Any suggestions? ("notePad" is my UITextView)?

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    //NSLog(@"KeyboardSize: %f.%f", kbSize.width, kbSize.height);

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, (kbSize.width > kbSize.height ? 
    kbSize.height : kbSize.width), 0);
    self.notePad.contentInset = contentInsets;
    self.notePad.scrollIndicatorInsets = contentInsets;
}
Brian Nickel
  • 26,890
  • 5
  • 80
  • 110
eEra
  • 59
  • 1
  • 7

6 Answers6

15

If you're using auto-layout at your views the following method may help you.

First define a IBOutlet for your bottom layout guide constraint and link with storyboard element.

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewBottomConst;

Second add observers for keyboard notifications.

- (void)observeKeyboard {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

Finally the methods that handles keyboard changes.

- (void)keyboardWillShow:(NSNotification *)notification {
    NSDictionary *info = [notification userInfo];
    NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];

    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    CGRect keyboardFrame = [kbFrame CGRectValue];

    CGRect finalKeyboardFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];

    int kbHeight = finalKeyboardFrame.size.height;

    int height = kbHeight + self.textViewBottomConst.constant;

    self.textViewBottomConst.constant = height;

    [UIView animateWithDuration:animationDuration animations:^{
        [self.view layoutIfNeeded];
    }];
}

- (void)keyboardWillHide:(NSNotification *)notification {
    NSDictionary *info = [notification userInfo];

    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    self.textViewBottomConst.constant = 10;

    [UIView animateWithDuration:animationDuration animations:^{
        [self.view layoutIfNeeded];
    }];
}

This method supports orientation changes and different keyboard sizes. Hope it helps.

Boran
  • 949
  • 10
  • 17
  • Great! Worked for me with a tableview /w editable cells. For more pleasing animation, this code pulls the parameters off the keyboard show/hide animation: NSTimeInterval duration = [notif.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; UIViewAnimationCurve curve = [notif.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; UIViewAnimationOptions options = (curve << 16) | UIViewAnimationOptionBeginFromCurrentState; [UIView animateWithDuration:duration delay:0.0 options:options animations:^{ [self.view layoutIfNeeded]; } completion:nil]; – Eli Burke Apr 03 '14 at 13:05
  • Thank you, BoranA! It is clear and works like a charm! – Dmitry Varavkin Nov 04 '14 at 16:30
  • There should be a action for UIKeyboardDidChangeFrameNotification because user can change input language while keyboard is presenting. – CarmeloS Jan 14 '15 at 01:37
  • This has been massively helpful, thanks so much! remember to add `[self observeKeyboard];` to viewDidLoad for this to work though! – Manesh Feb 25 '15 at 16:17
4

Your code is logically correct. When keyboard appear you shouldn't almost never change the frame of an object with the scrollview behaviour, but you should only change the insets. The insets should change relative to the current version because iOS7 take care of adjust for navigation bar. If you provide a new insets probably you will broke something in UI. Your code is broken on iOS7 for two main reason:

  1. You must add auto layout constraint to textview container. (Probably your text view is bigger then you expect)
  2. You shouldn't change insets in absolute way.

Here are the steps to properly configure a textview:

  • In xib (or storyboard) add constraint to top, left, right, bottom to the container (in my case {0, 0, 0, 0} as shown below

Autolayout constraint

  • 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];
    
  • In keyboardWillShow and keyboardWillHide don't change the frame, but change the insets relatively to the existing one.

    - (void)keyboardWillShow:(NSNotification *)notification
    {
        // Take frame with key: UIKeyboardFrameEndUserInfoKey because we want the final frame not the begin one
        NSValue *keyboardFrameValue = [notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
        CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
    
        UIEdgeInsets contentInsets = self.textView.contentInset;
        contentInsets.bottom = CGRectGetHeight(keyboardFrame);
    
        self.textView.contentInset = contentInsets;
        self.textView.scrollIndicatorInsets = contentInsets;
    }
    
    - (void)keyboardWillHide:(NSNotification *)notification
    {
        UIEdgeInsets contentInsets = self.textView.contentInset;
        contentInsets.bottom = .0;
    
        self.textView.contentInset = contentInsets;
        self.textView.scrollIndicatorInsets = contentInsets;
    }
    
    • Then remember to remove observers
Gigisommo
  • 1,232
  • 9
  • 9
3

@BoranA has the correct answer, but requires tweaking for full functionality for ALL keyboards.

Follow the code below:

Attach the below to your Vertical Space - Bottom Layout Guide - TextField

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewBottomConst;

Second add observers for keyboard notifications.

- (void)observeKeyboard {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

Add this to your viewDidLoad

[self observeKeyboard]; 

Finally the methods that handles keyboard changes.

- (void)keyboardWillShow:(NSNotification *)notification {
//THIS WILL MAKE SURE KEYBOARD DOESNT JUMP WHEN OPENING QUICKTYPE/EMOJI OR OTHER KEYBOARDS.
kbHeight = 0;
height = 0;
self.textViewBottomConst.constant = height;
self.btnViewBottomConst.constant = height;

    NSDictionary *info = [notification userInfo];
    NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];

    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    CGRect keyboardFrame = [kbFrame CGRectValue];

    CGRect finalKeyboardFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];

    int kbHeight = finalKeyboardFrame.size.height;

    int height = kbHeight + self.textViewBottomConst.constant;

    self.textViewBottomConst.constant = height;

    [UIView animateWithDuration:animationDuration animations:^{
        [self.view layoutIfNeeded];
    }];
}

- (void)keyboardWillHide:(NSNotification *)notification {
    NSDictionary *info = [notification userInfo];

    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    self.textViewBottomConst.constant = 10;

    [UIView animateWithDuration:animationDuration animations:^{
        [self.view layoutIfNeeded];
    }];
}
Manesh
  • 528
  • 6
  • 20
1

I found I had to call [self layoutIfNeeded] in order my insets to take effect.

My keyboard notification method looks like this (I prefer to animate the change):

-(void)keyboardWillShow:(NSNotification*)notification;
{
  NSDictionary *userInfo = [notification userInfo];
  NSValue *keyboardBoundsValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
  CGFloat keyboardHeight = [keyboardBoundsValue CGRectValue].size.width;

  CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  NSInteger animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];

  [UIView animateWithDuration:duration delay:0. options:animationCurve animations:^{
    [[self textView] setContentInset:UIEdgeInsetsMake(0., 0., keyboardHeight, 0.)];
    [[self view] layoutIfNeeded];
  } completion:nil];
}
petehare
  • 1,874
  • 16
  • 14
0

You need to resize your UITextView when your keyboard appears. So have a look to a previous answer I made here. You need to call the following method to resize your UITextView depending of the width of your keyboard and the text :

- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andWidth:(CGFloat)width
{
    UITextView *calculationView = [[UITextView alloc] init];
    [calculationView setAttributedText:text];
    CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
    return size.height;
}

Your code using my method :

- (void)keyboardWasShown:(NSNotification*)aNotification 
{ 
    NSDictionary* info = [aNotification userInfo]; 
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    // Get your text to NSAttributedString 
    NSAttributedString *as = [[NSAttributedString alloc] initWithString:self.notePad.text];

    // Resize UITextView
    self.notePad.frame = CGRectMake(0, 0, CGRectGetWidth(self.notePad.frame), [self textViewHeightForAttributedText:as andWidth:kbSize.width)]);
}
Community
  • 1
  • 1
Jordan Montel
  • 8,227
  • 2
  • 35
  • 40
0

I’d been battling with this for a week and I found that adding the keyboard size’s height to the bottom contentInset didn’t work.

What worked was subtracting it from the top, like so:

UIEdgeInsets insets = UIEdgeInsetsMake(-(kbSize.height), 0.0, 0.0, 0.0);
[self.textView setContentInset:insets];
magiclantern
  • 768
  • 5
  • 19