16

I have a UIViewCOntrollerthat contains a UITextView. When the keyboard appears I resize it like this:

#pragma mark - Responding to keyboard events

- (void)keyboardDidShow:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGRect keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect newTextViewFrame = self.textView.frame;
    newTextViewFrame.size.height -= keyboardSize.size.height + 70;
    self.textView.frame = newTextViewFrame;
    self.textView.backgroundColor = [UIColor yellowColor];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGRect keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect newTextViewFrame = self.textView.frame;
    newTextViewFrame.size.height += keyboardSize.size.height - 70;
    self.textView.frame = newTextViewFrame;
}

The textView seems to rezise to the right size, but when the user types the cursor ends up "outside" the textView frame. See picture below:

enter image description here

The yellow area is the UITextView frame (I don't know what the blue line next to the R key is). I find this quite wired. I'm using iOS7 if that makes any difference.

Any ideas or tips?

Update

I have a UITextView subclass that draws horizontal lines with the following method (if that makes any difference):

- (void)drawRect:(CGRect)rect {

    //Get the current drawing context
    CGContextRef context = UIGraphicsGetCurrentContext();
    //Set the line color and width
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:229.0/255.0 green:244.0/255.0 blue:255.0/255.0 alpha:1].CGColor);
    CGContextSetLineWidth(context, 1.0f);
    //Start a new Path
    CGContextBeginPath(context);

    //Find the number of lines in our textView + add a bit more height to draw lines in the empty part of the view
    NSUInteger numberOfLines = (self.contentSize.height + rect.size.height) / self.font.lineHeight;

    CGFloat baselineOffset = 6.0f;

    //iterate over numberOfLines and draw each line
    for (int x = 0; x < numberOfLines; x++) {
        //0.5f offset lines up line with pixel boundary
        CGContextMoveToPoint(context, rect.origin.x, self.font.lineHeight*x + 0.5f + baselineOffset);
        CGContextAddLineToPoint(context, rect.size.width, self.font.lineHeight*x + 0.5f + baselineOffset);
    }

    // Close our Path and Stroke (draw) it
    CGContextClosePath(context);
    CGContextStrokePath(context);
}
Anders
  • 2,903
  • 7
  • 58
  • 114
  • I've encountered the same issue. It seems that `textView.font.lineHeight` is not accounted for adjusting the `contentOffset`. Correcting the offset delegate's `textViewDidChange:` does not work as its value reverts back somehow. – Daniel Duan Aug 28 '13 at 15:39
  • Ok, how did you solve it? – Anders Aug 28 '13 at 17:44
  • Could this just be a problem with a certain unreleased, NDA'd SDK? Does it work with other iOS versions? – James Webster Sep 01 '13 at 11:49
  • It's just a hunch, but have you tried calling `[self.textView setNeedsLayout];` inside your keyboardDidShow and keyboardWillHide methods? – Tim Bodeit Sep 01 '13 at 22:31
  • And second idea, that I have is: if you are using AutoLayout, try setting a height constraint. Connect that constraint as an IBOutlet with your view controller. Instead of changing the frame, try changing the auto layout constraint. Please let me know if any of this works for you. – Tim Bodeit Sep 01 '13 at 22:34
  • I've reported the issue to radar with sample code. – Daniel Duan Sep 02 '13 at 19:01
  • @TimBodeit, tried `[self.textView setNeedsLayout]` didn't help. – Anders Sep 02 '13 at 20:16
  • @Anders You can just use an `UITableViewController` and put all your content in there, this way you don't have to deal with any of this, since it's all automatically handled for you. Is that possible? – Raúl Juárez Sep 02 '13 at 21:30
  • Increasing the frame of the text view seems pretty odd to me. Couldn't you place it inside a scrollview so that you can offset the content of the scrollview instead? – mattyohe Sep 02 '13 at 21:56
  • @mattyohe A text view is already a scrollview. Why would he place it there? – Léo Natan Sep 05 '13 at 14:27
  • This was helpful to me, not 100% perfect but better than all the bits of code I tested from stack overflow. [https://github.com/steipete/PSPDFTextView](https://github.com/steipete/PSPDFTextView) – Jason Feb 24 '14 at 18:54

9 Answers9

20

Instead of resizing the frame, why not give your text view a contentInset (and a matching scrollIndicatorInsets)? Remember that text views are actually scrollviews. This is the correct way to handle keyboard (or other) interference.

For more information on contentInset, see this question.


This seems to not be enough. Still use insets, as this is more correct (especially on iOS7, where the keyboard is transparent), but you will also need extra handling for the caret:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.textView setDelegate:self];
    self.textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
}

- (void)_keyboardWillShowNotification:(NSNotification*)notification
{
    UIEdgeInsets insets = self.textView.contentInset;
    insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
    self.textView.contentInset = insets;

    insets = self.textView.scrollIndicatorInsets;
    insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
    self.textView.scrollIndicatorInsets = insets;
}

- (void)_keyboardWillHideNotification:(NSNotification*)notification
{
    UIEdgeInsets insets = self.textView.contentInset;
    insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
    self.textView.contentInset = insets;

    insets = self.textView.scrollIndicatorInsets;
    insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
    self.textView.scrollIndicatorInsets = insets;
}

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _oldRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    [_caretVisibilityTimer invalidate];
    _caretVisibilityTimer = nil;
}

- (void)_scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);

        [self.textView setContentOffset:newOffset animated:YES];
    }
}

-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

A lot of work, Apple should provide better way of handling the caret, but this works.

Community
  • 1
  • 1
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Tested, but I get the same result. – Anders Sep 07 '13 at 08:53
  • I'll create a text project and try to reproduce your issue and a fix. – Léo Natan Sep 07 '13 at 18:43
  • Thanks, got it to work. Wanted to wait for the GM to see if they would have changed it, but they didn't. This was easier with iOS6! – Anders Sep 11 '13 at 17:28
  • There are no changes in the GM. – Léo Natan Sep 11 '13 at 17:30
  • I noticed one small problem. If the user "activates" the textView by pressing at or near the bottom, then the cursor will end up "above" the view and be hidden. Guess this only happens if the user have previously entered a couple of lines, then hides the keyboard, and then presses at the bottom. Do you have an ideas on how to fix that issue? – Anders Sep 11 '13 at 17:34
  • You need to add more guards and take more scenarios into account when calculating the new offset. My example above was a rough around the edges idea, and it may require some little work. – Léo Natan Sep 11 '13 at 20:10
  • This works to an extent in portrait mode but fails to work in Landscape. Any fixes for this yet Leo? – Josh Sep 24 '13 at 22:12
  • Can you please explain where it fails in landscape? It is possible that some of my calculations did not take orientation in account, but in theory, solution should work for landscape and portrait alike. – Léo Natan Sep 24 '13 at 23:17
  • @LeoNatan Just reading this question now. It appears that when in landscape mode, the textview scrolls down, and moves where you're typing - up (and above where you can actually see it). Example: [Screenshot](http://s9.postimg.org/d037sknkv/Screenshot.png) – JDev Dec 07 '13 at 19:51
  • @MattBush Perhaps my calculations in the example are not accurate for landscape. I did this as a quick example. Feel free to expand upon. This is a shorter version of our own rich text editor we have in our app. – Léo Natan Dec 07 '13 at 20:12
  • 1
    Just one thing to note here. This is the best approach for iOS 7 and iOS 8. But you need to have in mind that UIKeyboardWillShowNotification is called each time the keyboard changes. This means if you collapse the predictive typing panel in iOS 8 this will change the keyboard height and trigger the event with different height. The user can also split the keyboard without dismissing it. So instead of doing += to the bottom inset you should really do a '='. This way you will save yourself some trouble. The same thing goes for the hiding event, do not -= the inset, just set it directly to 0. – Angel Naydenov Feb 11 '15 at 14:42
  • @Angel You are correct, but don't forget the system also throws a `WillHide` notification which balances the + and -. – Léo Natan Feb 11 '15 at 14:54
  • @LeoNatan It is possible that I've done something wrong but when I wrote it the insets became too large when I've split and un-split the keyboard multiple times without hiding it (as on the iPad the user can hide it manually even if it is not displayed with interactive hide option). The result was that I was not able to see text at all. I've written it for iOS 8 with Swift which should not really matter but just to make sure it is known. Other than that your code really helped figure this one out, thanks! – Angel Naydenov Feb 11 '15 at 18:37
  • Make sure to implement `UIKeyboardWillHideNotification` as well and do the reverse. I remember there were some issues with `UIKeyboardWillChangeFrameNotification` and iOS8 betas. – Léo Natan Feb 11 '15 at 18:41
6

All of the others answers I tried behaved somewhat strangely for me. Using an NSTimer to perform the scroll also meant that the user couldn't scroll up, since the caret would then end up off-screen and it would immediately scroll back down again. In the end I stuck with the original approach of changing the UITextView frame on the keyboard notification events, then added the following methods:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    // Whenever the user enters text, see if we need to scroll to keep the caret on screen
    [self scrollCaretToVisible];
    return YES;
}

- (void)scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    // Convert into the correct coordinate system
    caretRect = [self.view convertRect:caretRect fromView:self.textView];

    if(CGRectEqualToRect(caretRect, _oldRect)) {
        // No change
        return;
    }

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.frame;

    //We will scroll only if the caret falls outside of the visible rect.
    if (!CGRectContainsRect(visibleRect, caretRect))
    {
        // Work out how much the scroll position would have to change by to make the cursor visible
        CGFloat diff = (caretRect.origin.y + caretRect.size.height) - (visibleRect.origin.y + visibleRect.size.height);

        // If diff < 0 then this isn't to do with the iOS7 bug, so ignore
        if (diff > 0) {
            // Scroll just enough to bring the cursor back into view
            CGPoint newOffset = self.textView.contentOffset;
            newOffset.y += diff;
            [self.textView setContentOffset:newOffset animated:YES];
        }
    }
}

Works like a charm for me

andygeers
  • 6,909
  • 9
  • 49
  • 63
  • Your line defining "diff" is the correct one. However, I'd recommend adding _textView.font.lineHeight*2 to diff as well, otherwise if the user enters a blank line at the bottom of the textView, part of the cursor will end up offscreen. – Colin Jul 18 '14 at 20:23
4

A lot of answers already, I found that in my case it's actually much simpler. On keyboardWillShow I adjust the text view's contentInset and keep the frame full screen. And while scrollRangeToVisible: is not working for me like for so many others, the scroll view methods (from which UITextView inherits) work just fine. This works for me:

- (void)textViewDidChange:(UITextView *)textView
{
    CGRect caret = [_textView caretRectForPosition:_textView.selectedTextRange.end];
    [_textView scrollRectToVisible:caret animated:YES];
}
Pascal
  • 16,846
  • 4
  • 60
  • 69
  • This is a good answer for me. My UITextView was inside a UIScrollView so I had to not only get the caret position but also append the textView's frame origin y position before my UIScrollView scrolled to the correct UITextView cursor position. – Zhang Jul 03 '15 at 06:20
3

Anders and Leo Natan have great solutions. However, I needed to modify their answers a little to get the scrolling to work properly with contentInset. The problem I faced was that textViewDidBeginEditing: gets called before keyboardWasShown: so the contentInset change does not get reflected the first time through. Here is what I did:

In .h

@interface NoteDayViewController : UIViewController <UITextViewDelegate>
{
    UIEdgeInsets noteTextViewInsets;
    UIEdgeInsets noteTextViewScrollIndicatorInsets;
    CGRect oldRect;
    NSTimer *caretVisibilityTimer;
    float noteViewBottomInset;
}
@property (weak, nonatomic) IBOutlet UITextView *noteTextView;

In .m

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWasShown:)
                                             name:UIKeyboardDidShowNotification object:nil];

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

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    CGFloat kbHeight = // get the keyboard height following your usual method

    UIEdgeInsets contentInsets = noteTextViewInsets;
    contentInsets.bottom = kbHeight;
    noteTextView.contentInset = contentInsets;

    UIEdgeInsets scrollInsets = noteTextViewScrollIndicatorInsets;
    scrollInsets.bottom = kbHeight;
    noteTextView.scrollIndicatorInsets = scrollInsets;

    [noteTextView setNeedsDisplay];
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{    
    noteTextView.contentInset = noteTextViewInsets;
    noteTextView.scrollIndicatorInsets = noteTextViewScrollIndicatorInsets;   
    [noteTextView setNeedsDisplay];
}

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    oldRect = [noteTextView caretRectForPosition:noteTextView.selectedTextRange.end];
    noteViewBottomInset = noteTextView.contentInset.bottom;
    caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    [caretVisibilityTimer invalidate];
    caretVisibilityTimer = nil;
}

- (void)scrollCaretToVisible
{
    // This is where the cursor is at.
    CGRect caretRect = [noteTextView caretRectForPosition:noteTextView.selectedTextRange.end];

    // test if the caret has moved OR the bottom inset has changed
    if(CGRectEqualToRect(caretRect, oldRect) && noteViewBottomInset == noteTextView.contentInset.bottom)
    return;

    // reset these for next time this method is called
    oldRect = caretRect;
    noteViewBottomInset = noteTextView.contentInset.bottom;

    // this is the visible rect of the textview.
    CGRect visibleRect = noteTextView.bounds;
    visibleRect.size.height -= (noteTextView.contentInset.top + noteTextView.contentInset.bottom);
    visibleRect.origin.y = noteTextView.contentOffset.y;

    // We will scroll only if the caret falls outside of the visible rect.
    if (!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = noteTextView.contentOffset;
        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height, 0);
        [noteTextView setContentOffset:newOffset animated:NO]; // must be non-animated to work, not sure why
    }
}
BenK
  • 111
  • 3
  • 3
1

This is what I ended up doing, and something that seems to work:

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

    NSDictionary* info = [notification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

     // self.textViewBottomSpace.constant = NSLayoutConstraint in IB (bottom position)
    self.textViewBottomSpace.constant = kbSize.height + 70;
    [self.textView setNeedsDisplay];
}


- (void)textViewKeyboardWillHide:(NSNotification *)notification
{
    self.textViewBottomSpace.constant = 0;
    [self.textView setNeedsDisplay];
}

- (void)scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect)) {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 10, 0);

        [self.textView setContentOffset:newOffset animated:YES];
    }
}

- (void)textViewDidEndEditing:(UITextView *)textView
{    
    [_caretVisibilityTimer invalidate];
    _caretVisibilityTimer = nil;
}

- (void)textViewDidBeginEditing:(UITextView *)textView
{ 
    self.oldRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];
    self.caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(scrollCaretToVisible) userInfo:nil repeats:YES];
}
Anders
  • 2,903
  • 7
  • 58
  • 114
  • hi Anders, could you explain this line self.textviewBottomSpace.constant I can't seem to find it's declaration in your code snippet. Thanks! – Josh Sep 24 '13 at 21:56
  • Ok, cool! Changeing the frame the old-school way should have the same effect I think. – Anders Sep 25 '13 at 16:58
  • I can confirm that this solution works on the frame-resizing method. – Daniel Duan Sep 29 '13 at 16:36
1

A simpler solution to this problem is to update the text view frame in response to the textViewDidBegingEditing delegate method. For further details, see the following:

How to re-size UITextView when keyboard shown with iOS 7

Community
  • 1
  • 1
ColinE
  • 68,894
  • 15
  • 164
  • 232
1

For those that have a UITextView inside a UIScrollView where iOS < 7 took care of scrolling the caret into view: Here's how it works with iOS 7 (and also 5 & 6).

// This is the scroll view reference
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;

// Track the current UITextView
@property (weak, nonatomic) UITextView *activeField;

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    self.activeField = textView;
}

- (void)textViewdDidEndEditing:(UITextView *)textView
{
    self.activeField = nil;
}

// Setup the keyboard observers that take care of the insets & initial scrolling
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWasShown:)
                                             name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillBeHidden:)
                                             name:UIKeyboardWillHideNotification object:nil];

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

    UIEdgeInsets insets = self.vForm.contentInset;
    insets.bottom += kbSize.height;
    self.vForm.contentInset = insets;

    insets = self.vForm.scrollIndicatorInsets;
    insets.bottom += kbSize.height;
    self.vForm.scrollIndicatorInsets = insets;

    // Scroll the active text field into view
    CGRect aRect = self.vForm.frame;
    aRect.size.height -= kbSize.height;
    CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y);
    [self.scrollView setContentOffset:scrollPoint animated:YES];
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    self.vForm.contentInset = contentInsets;
    self.vForm.scrollIndicatorInsets = contentInsets;
}

// This is where the magic happens. Set the class with this method as the UITextView's delegate.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    // Scroll the textview to the caret position
    [textView scrollRangeToVisible:textView.selectedRange];

    // Scroll the scrollview to the caret position within the textview
    CGRect targetRect = [textView caretRectForPosition:textView.selectedTextRange.end];
    targetRect.origin.y += self.activeField.frame.origin.y;
    [self.scrollView scrollRectToVisible:targetRect animated:YES];

    return YES;
}

I tried to include most of the required glue code. The only things missing are setting the UITextView's delegate and dismissing the keyboard.

Took 2-3 days to figure out what previously worked. Thanks, Apple.

domsom
  • 3,163
  • 1
  • 22
  • 27
1

Angel Naydenov's comment above is right, especially in cases such as switching from English to Japanese keyboard that shows suggests.

When switching keyboards, UIKeyboardWillShowNotification is called but UIKeyboardWillHideNotification is not called.

So you must adjust the inset to use the absolute value and not use +=.

Unrelatedly, [self.textView setContentOffset:newOffset animated:YES]; will not actually change the graphics in iOS 7.1 after the keyboard is shown for the second time, which is probably a bug. A workaround I used is replacing

[self.textView setContentOffset:newOffset animated:YES]; 

with

[UIView animateWithDuration:.25 animations:^{
        self.textView.contentOffset = newOffset;
 }];
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
ykonda
  • 499
  • 6
  • 15
0

Leo Natan, you started out well but your execution was relatively inefficient. Here is a better way of doing it with less code:

// Add Keyboard Notification Listeners in ViewDidLoad
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];


// And Add The Following Methods
- (void)_keyboardWillShowNotification:(NSNotification*)notification
{    
    CGRect textViewFrame = self.textView.frame;
    textViewFrame.size.height -= ([notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height + 4.0);
    self.textView.frame = textViewFrame;
}

- (void)_keyboardWillHideNotification:(NSNotification*)notification
{
    CGRect textViewFrame = self.textView.frame;
    textViewFrame.size.height += ([notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height + 4.0);
    self.textView.frame = textViewFrame;
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {

    NSRange typingRange = NSMakeRange(textView.text.length - 1, 1);
    [textView scrollRangeToVisible:typingRange];

    return YES;

}

- (void)dealloc {

    [[NSNotificationCenter defaultCenter] removeObserver:self];

}
HomeGrown
  • 27
  • 4
  • 4
    This isn't a more efficient implementation of Leo Natan's solution, it's a different solution that doesn't address Leo's iOS 7 keyboard transparency concern. As Leo notes, using contentInset and scrollIndicatorInsets is the more "correct" solution, as opposed to just changing the frame size. – Oran Dennison Oct 21 '13 at 21:50