5

我一直在为 iOS 7 重新编写我的应用程序。它现在使用新的 TextKit API。我几乎完成了,但自从我开始进行过渡以来,还有一个问题一直让我头疼。

问题:当我在 UITextView 中输入文本时,有时视图会向上滚动隐藏光标。很难确定模式,但它似乎发生在我删除一行并且下面的文本必须向上移动之后。但只有第二次我删除了一行,并且只删除了文档中的某些行(通常只有换行符但并不总是......)

我曾尝试在 stackoverflow 上使用这些解决方案:

输入时如何阻止UITextView向上滚动

UITextView 在每插入一个新行后都会滚动,所以这些行是不可见的(iPhone OS)

在 iOS 7 中滚动到 UITextView 不稳定的底部

当 UITextView 中有 UITableView 时,还有许多其他问题导致滚动无法正常工作。我的视图中没有表视图。

我想指出,我已经继承了 UITextView 并发现它肯定是 iOS 7 调用不仅向上滚动视图。此外,更奇怪的是,当您按下后退按钮(从 UITextView 中删除一个字符)时,它会向textViewDidChangeSelection:. 这是发生问题时的调用堆栈。(当问题没有发生时,我仍然会接到两次委托委托textViewDidChangeSelection:……这似乎是正常的):

  • UITextView 委托textViewDidChangeSelection:包含要删除的字符的位置,长度为 1。(即 1050,1)
  • 再次调用UITextView 委托textViewDidChangeSelection:并包含相同的位置但长度为零。(即 1050,0)
  • scrollViewDidScroll(最多 3 次。但一般只有一次)
  • 然后 iOS 7 调用UITextView.scrollRangeToVisible:FIRST 调用的范围为 {1050,1}。这对我来说毫无意义。我知道这是在内部调用的,而不是从我的任何代码中调用的,因为调用此内部 API时没有_ensureselectionvisible任何回溯到我可能用来调用滚动事件的函数。

大概前两个调用只是选择字符,然后在内部调用 deleteBackwards。这就说得通了。不过在那之后我就瞎了。我不知道它为什么会滚动或为什么会调用scrollRangeToVisible:错误的范围。

我已经能够通过在用户编辑文本时UITextView.scrollRangeToVisible:返回 w/o call来覆盖来缓解这个问题。[super scrollRangeToVisible]我通过覆盖一些滚动视图委托调用并设置一个表示不能滚动的标志来做到这一点。对我来说,这是一个巨大的 UGLY hack,并且在某些情况下仍然会出现问题 - 例如,当我在第一次编辑时点击视图时,有时当滚动视图停止减速时。

简而言之,发生的事情是发生了滚动事件(可能是由于上一行向上移动)并且iOS出于某种原因认为光标不在视图中。更奇怪的是,即使提供了错误的范围,它确实提供了正确的位置,从而将其滚动到视图中的错误位置。更奇怪的是,它只发生在某些条件下,并且只发生在条件发生的第二种情况下。我唯一能想到的是,删除文本后 UITextView 的高度计算不正确,它认为文本的位置与实际位置不同。

提前致谢!

更新:

我设置了我之前提到的标志scrollViewDidScroll:,它不会阻止其他情况下的跳跃。但是,仍然存在向后删除文本仍然会跳过屏幕的情况。在视图仍然跳转的情况下更奇怪的是我看到我正在阻止[super scrollRangeToVisible]被调用!所以在内部发生了一些事情,另外,这导致视图滚动。

更新:

它似乎在 613 点和 670 点之间向上跳跃。我不确定是什么导致了分数的差异。它跳跃的行数也不同。我怀疑条件之间必须有相似之处。此外,当scrollViewDidScroll:被调用时,我验证了textView.selectedRange.location此调用与委托调用之间的相同textViewDidChangeSelection:

4

2 回答 2

9

这是最新版本的 iOS 中的一个错误。希望它会尽快修复,直到那时据我所知,唯一的选择是手动告诉 UITextView 在选择更改时滚动到光标位置:

// in the text view's delegate
- (void)textViewDidChangeSelection:(UITextView *)textView
{
  [textView scrollRangeToVisible:textView.selectedRange];
}

这对我有用,但也许你的应用程序的错误略有不同。不知道你应该怎么做……如果你不能解决,我会向苹果公司提交一份 TSI。https://developer.apple.com/support/technical/submit/

于 2014-01-19T19:45:39.283 回答
0

由于scrollRangeToVisible:triggers scrollViewDidScroll:,如果您同时观看UITextViewDelegateand UIScrollViewDelegate,则会导致一些行为异常。setContentOffset:也会这样做。为了防止这种情况,您需要设置scrollView.bounds. 我没有使用textViewDidChangeSelection:,而是textViewDidBeginEditing:

- (void)textViewDidBeginEditing:(UITextView *)textView {
    CGRect caret = [textView caretRectForPosition:textView.selectedTextRange.start];
    UIEdgeInsets textInsets = textView.textContainerInset;
    CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
    // only reposition the scroll view if the caret position is out of view
    if (textViewHeight < caret.origin.y) {
        CGSize textSize = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size;
        // initially place the view such that the last part of the text is visible
        CGFloat offsetY = textSize.height - textViewHeight;
        // test to see if the caret is in the middle of the text somewhere
        if ((caret.origin.y + (textViewHeight / 2)) <= textSize.height) {
            // we can, so center the caret in the middle of the view
            offsetY = caret.origin.y - (textViewHeight / 2);
        }
        // the offset indicates the point in the scrollView that will correspond to the top left of the scrollView box
        // therefore, we need to subtract the text view height to place the caret at the bottom of the scrollView, rather than the top and some empty whitespace
        [self repositionScrollView:textView newOffset:CGPointMake(caret.origin.x, offsetY)];
    }
}

/**
 This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
 */
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
    CGRect scrollBounds = scrollView.bounds;
    scrollBounds.origin = offset;
    scrollView.bounds = scrollBounds;
}

contentOffset永远不会大于,因此textSize.height - textViewHeight底部不会有任何空格。如果文本有点长,那么代码会将插入符号置于 ; 中间UITextView。恕我直言,这是最好的行为,因为它为用户提供了插入符号上方和下方的文本上下文。

于 2015-06-25T18:21:48.033 回答