Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

-scrollRangeToVisible: doesn't take keyboard size into account in iOS 7

I've been using the following code in my view controller to update the content offset of a UITextView when the keyboard gets displayed:

- (void)keyboardWasShown:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

    UIEdgeInsets contentInsets = UIEdgeInsetsMake( 0.0, 0.0, keyboardRect.size.height, 0.0 );
    self.textView.contentInset = contentInsets;
    self.textView.scrollIndicatorInsets = contentInsets;
}

With the keyboard showing, manually scrolling the content of the UITextView to the bottom has it properly ending just above the top of the keyboard. -[UITexView scrollRangeToVisible:], however, doesn't seem to take into account the presence of the keyboard any more.

  • In iOS 6, the text view scrolled until the specified range was displayed just above the keyboard.
  • In iOS 7, the visibility appears to now be based on the frame of the text view and not the content inset, as it used to. So the view will only scroll when the range extends below the frame, and then it will scroll only enough to get that range visible at the bottom of the

Visually, here's what's happening. I built an inline search for my text view with controls to jump between the results (similar to searching in Safari). So in the text view shown here with search results as the user tapped the "next" button, the cyan selection would cycle down through the results. When the user went to the seventh result, the view would scroll until it was visible.

With the keyboard (from the UISearchBar) up on the same search results when the user went to the fifth search result, it would scroll to be just above the keyboard. But only in iOS 6. In iOS 7 no scrolling happens until going to the seventh search result like in the non-keyboard situation, and even then it scrolls the same amount so it's just visible below the bottom of the text view's frame.

Is this a known change in iOS 7? I'm using auto-layout so the next thing I'm going to try is to adjust the text view's bottom spacing constraint to shrink the entire view to avoid the problem, but want to check if there's way to still use my existing code under iOS 7.

like image 239
Jeff Nouwen Avatar asked Sep 26 '13 15:09

Jeff Nouwen


People also ask

How do I fix the keyboard size on my iPhone?

To do that, open the Settings app on your iPhone. Inside Settings, select Display & Brightness. On the next screen, tap on Display Zoom under “Display”. Here, select Zoomed to switch to a larger view and then tap on Set at the top right corner.

Why is my keyboard not working on my iPhone 7?

iPhone keyboards typically stop working for one of three reasons: The app you're trying to use the iPhone keyboard in has crashed. Your iPhone is experiencing a more advanced software problem. Your iPhone's display isn't working properly or has become unresponsive.

How do I change keyboard settings in iOS?

Go to Settings > General > Keyboard > Keyboards. Tap a language at the top of the screen, then select an alternative layout from the list.


2 Answers

Even though this has been already answered, I had the same problem while building my own UITextView subclass with search highlighting (it's available on my GitHub, if you're interested) and came up with a custom implementation of the scrollRangeToVisible: method. All you need to do is to adjust the contentInset and scrollIndicatorInset properties of your UITextView as you're already doing (related answer for casual Googlers reading this), then call:

[textView scrollRangeToVisible:range consideringInsets:YES];

I wrapped up the relevant code in a category, which also has a couple other useful methods to account for insets in iOS 7:

Note: you need them all due to how I organized this code in my subclass. Feel free to reorganize it to your liking.

@interface UITextView (insets)

// Scrolls to visible range, eventually considering insets
- (void)scrollRangeToVisible:(NSRange)range consideringInsets:(BOOL)considerInsets;

// Scrolls to visible rect, eventually considering insets
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated consideringInsets:(BOOL)considerInsets;

// Returns visible rect, eventually considering insets
- (CGRect)visibleRectConsideringInsets:(BOOL)considerInsets;

@end

@implementation UITextView (insets)

// Scrolls to visible range, eventually considering insets
- (void)scrollRangeToVisible:(NSRange)range consideringInsets:(BOOL)considerInsets
{
    if (considerInsets && (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1))
    {
        // Calculates rect for range
        UITextPosition *startPosition = [self positionFromPosition:self.beginningOfDocument offset:range.location];
        UITextPosition *endPosition = [self positionFromPosition:startPosition offset:range.length];
        UITextRange *textRange = [self textRangeFromPosition:startPosition toPosition:endPosition];
        CGRect rect = [self firstRectForRange:textRange];

        // Scrolls to visible rect
        [self scrollRectToVisible:rect animated:YES consideringInsets:YES];
    }
    else
        [self scrollRangeToVisible:range];
}

// Scrolls to visible rect, eventually considering insets
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated consideringInsets:(BOOL)considerInsets
{
    if (considerInsets && (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1))
    {
        // Gets bounds and calculates visible rect
        CGRect bounds = self.bounds;
        UIEdgeInsets contentInset = self.contentInset;
        CGRect visibleRect = [self visibleRectConsideringInsets:YES];

        // Do not scroll if rect is on screen
        if (!CGRectContainsRect(visibleRect, rect))
        {
            CGPoint contentOffset = self.contentOffset;
            // Calculates new contentOffset
            if (rect.origin.y < visibleRect.origin.y)
                // rect precedes bounds, scroll up
                contentOffset.y = rect.origin.y - contentInset.top;
            else
                // rect follows bounds, scroll down
                contentOffset.y = rect.origin.y + contentInset.bottom + rect.size.height - bounds.size.height;
            [self setContentOffset:contentOffset animated:animated];
        }
    }
    else
        [self scrollRectToVisible:rect animated:animated];
}

// Returns visible rect, eventually considering insets
- (CGRect)visibleRectConsideringInsets:(BOOL)considerInsets
{
    CGRect bounds = self.bounds;
    if (considerInsets)
    {
        UIEdgeInsets contentInset = self.contentInset;
        CGRect visibleRect = self.bounds;
        visibleRect.origin.x += contentInset.left;
        visibleRect.origin.y += contentInset.top;
        visibleRect.size.width -= (contentInset.left + contentInset.right);
        visibleRect.size.height -= (contentInset.top + contentInset.bottom);
        return visibleRect;
    }
    return bounds;
}

@end
like image 179
Ivano.Bilenchi Avatar answered Nov 14 '22 23:11

Ivano.Bilenchi


This seems to be a bug in iOS7. I am using following code as a work around (heavily inspired by the answers to following questions: How to re-size UITextView when keyboard shown with iOS 7).

CGRect caret_rect = [_editTextView caretRectForPosition:_editTextView.selectedTextRange.end];
UIEdgeInsets insets = _editTextView.contentInset;
CGRect visible_rect = _editTextView.bounds;
visible_rect.size.height -= (insets.top + insets.bottom);
visible_rect.origin.y = _editTextView.contentOffset.y;
if(!CGRectContainsRect(visible_rect, caret_rect)) {
    CGFloat new_offset = MAX((caret_rect.origin.y + caret_rect.size.height) - visible_rect.size.height - _editTextView.contentInset.top,  - _editTextView.contentInset.top);
    [_editTextView setContentOffset:CGPointMake(0, new_offset) animated:NO];
}

Oddly, it is not possible to change animated to YES in the last call.

I will file a bug report with Apple.

like image 30
Klaus Thul Avatar answered Nov 15 '22 00:11

Klaus Thul