在iOS 7中,-scrollRangeToVisible:方法没有考虑键盘的大小。

7
我一直在我的视图控制器中使用以下代码,在键盘显示时更新UITextView的内容偏移量:
- (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;
}

在键盘显示时,手动滚动UITextView的内容到底部,使其正确地停在键盘顶部的正上方。然而,-[UITexView scrollRangeToVisible:]似乎不再考虑键盘的存在。
在iOS 6中,文本视图会滚动直到指定范围显示在键盘正上方。
在iOS 7中,可见性似乎现在基于文本视图的框架而不是内容插图,就像以前一样。因此,只有当范围延伸到框架下方时,视图才会滚动,然后它只会滚动足够使该范围在文本视图底部可见。
从视觉上讲,这里发生了什么。我为我的文本视图构建了一个内联搜索,带有控件可以在结果之间跳转(类似于在Safari中搜索)。因此,在文本视图这里显示搜索结果中,当用户点击“下一个”按钮时,青色选择将循环浏览结果。当用户到达第七个结果时,视图会滚动,直到它可见。
当用户使用键盘(来自UISearchBar)在同一搜索结果中向上,当用户到达第五个搜索结果时,它将滚动到键盘正上方。但仅限于iOS 6。在iOS 7中,直到进入第七个搜索结果才会发生滚动,就像在非键盘情况下一样,即使这样,它也会滚动相同的量,因此它只能在文本视图框架底部下方可见。

这是iOS 7中已知的更改吗?我正在使用自动布局,所以我要尝试的下一件事是调整文本视图的底部间距约束,以缩小整个视图以避免问题,但是想检查是否有方法在iOS 7下仍然使用我的现有代码。


请参考以下链接:https://dev59.com/MWMk5IYBdhLWcg3wyw0U - Ri_
2个回答

5
尽管这个问题已经被回答过,但我在构建自己的带有搜索高亮功能的UITextView子类时遇到了同样的问题(如果您感兴趣,可以在我的GitHub上找到它),并想出了一个scrollRangeToVisible:方法的自定义实现。你所需要做的就是像你已经做的那样调整你的UITextViewcontentInsetscrollIndicatorInset属性(对于阅读此文的普通谷歌用户,相关答案),然后调用:
[textView scrollRangeToVisible:range consideringInsets:YES];

我将相关代码封装在一个类别中,该类别还有几个其他有用的方法来解决iOS 7中的插图问题:

注意:由于我在子类中组织代码的方式,您需要使用它们。请随意重新组织它以符合您的喜好。

@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

抱歉回复晚了,但是你的代码很好用(尽管我不得不将第31行从super改为self,因为UITextView的继承)。我已将你的解决方案标记为被接受的答案,因为它是最“轻量级”的解决方法。 - Jeff Nouwen
@Exile.90,使用您的代码在iOS 7.0.4中仍存在一个错误。当您滚动带有一些文本的文本视图,然后轻击它并按回车键(不输入任何内容),文本视图偶尔会跳到另一个位置。 - Indoor
我知道这个问题。不幸的是,这是UITextView的一个bug,我仍在寻找解决方法。在我的UITextView子类中,您只需键入另一个字符,它就会再次跳到光标位置(这是我对“光标超出可见范围”错误的修复结果)。UITextView已经彻底损坏了,唉。如果您有兴趣,请查看ICTextView。它比标准UITextView有更多的错误修复,您可能会发现它更容易使用。此外,如果您找到了一个不错的解决方案,请随时为其做出贡献。 - Ivano.Bilenchi
尝试了您的代码,但它的行为与原始的scrollRangeToVisible完全相同,忽略了键盘inputAccessoryView的边界。您是否考虑到了inputAccessoryView的存在? - Pedro Borges
1
@PedroBorges 我编写的代码不需要考虑 inputAccessoryView。您应该根据键盘的实际大小设置 UITextViewcontentInset 属性,并考虑 inputAccessoryView。一旦您这样做了,代码将按预期工作。您可以查看我为 ICTextView 编写的示例应用程序,它使用 UIToolBar 作为 inputAccessoryView - Ivano.Bilenchi
谢谢你的回答,我之前不知道UITextView.contentInset属性。 - Pedro Borges

3
这似乎是iOS7中的一个bug。我正在使用以下代码作为解决方法(受以下问题答案的启发:如何在iOS 7中显示键盘时重新调整UITextView的大小)。
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];
}

奇怪的是,在最后一次调用中无法将animated更改为YES。

我将向苹果提交错误报告。


实际上,我猜想我要么得改变文本视图的框架,要么执行类似于你代码片段中的操作。但是,越想越觉得这不完全是一个 bug,或者至少苹果可能不认为它是一个 bug。具体来说,这是由于 iOS 7 中键盘现在是半透明的,就像一个半透明的导航栏一样,它并没有被认为遮挡了下面的内容。唉,好吧,我也会在上面提交一个 bug! - Jeff Nouwen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接