【问题标题】:UITextView scrolling up after deleting/inserting text删除/插入文本后 UITextView 向上滚动
【发布时间】:2014-02-08 20:04:56
【问题描述】:

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

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

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

How to stop UITextView from scrolling up when entering it

UITextView keep scrolling after every new line is inserted, so the lines are invisible (iPhone OS)

Scroll to bottom of UITextView erratic in iOS 7

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

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

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

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

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

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

提前致谢!

更新:

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

更新:

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

【问题讨论】:

    标签: ios objective-c uitextview uitextviewdelegate


    【解决方案1】:

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

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

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

    【讨论】:

    • 不幸的是,这并没有解决我的问题。不过谢谢你的回复!
    • 我错了!它确实解决了我的问题。我仍然有一些逻辑可以防止在某些情况下发生滚动。非常感谢!
    【解决方案2】:

    由于scrollRangeToVisible:触发scrollViewDidScroll:,如果你同时看UITextViewDelegateUIScrollViewDelegate,会导致一些行为异常。 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 的中间;恕我直言,这是最好的行为,因为它为用户提供了插入符号上方和下方的文本上下文。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-06
      • 2011-03-11
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      相关资源
      最近更新 更多