5

iOS 7 用にアプリを書き直しています。現在、新しい TextKit API を使用しています。ほぼ完了しましたが、移行を開始して以来、頭痛の種となっている問題がもう 1 つあります。

問題: UITextView にテキストを入力すると、ビューがスクロールアップしてカーソルが非表示になることがあります。パターンを特定するのは難しいですが、行を削除した後に発生するようで、下のテキストを上に移動する必要があります. ただし、行を削除するのは2回目で、ドキュメント内の特定の行のみです(通常、改行文字のみを含む行ですが、常にではありません...)

私はstackoverflowでこれらのソリューションを使用してみました:

入力時にUITextViewが上にスクロールしないようにする方法

新しい行が挿入されるたびに UITextView がスクロールし続けるため、行が見えない (iPhone OS)

iOS 7 で不安定な UITextView の一番下までスクロールする

UITextView 内に UITableView がある場合、スクロールが正しく機能しないという他の多くの問題もあります。ビューにテーブル ビューがありません。

UITextView をサブクラス化し、ビューを上にスクロールするだけでなく、iOS 7 が呼び出しを行っていることを発見したことを指摘したいと思います。また、さらに奇妙なのは、(UITextView から文字を削除するために) 戻るボタンを押すと、2 つのメッセージが に送信されることtextViewDidChangeSelection:です。問題が発生したときのコール スタックを次に示します。(問題が発生しない場合でも、デリゲートへの2つの呼び出しが発生しますtextViewDidChangeSelection:...これは正常なようです):

  • UITextView デリゲートtextViewDidChangeSelection:には、長さ 1 で削除する文字の位置が含まれます。(つまり、1050,1)
  • UITextView デリゲートtextViewDidChangeSelection:が再度呼び出され、同じ位置が含まれますが、長さはゼロです。(つまり、1050,0)
  • scrollViewDidScroll (最大 3 回。ただし、通常は 1 回のみ)
  • 次に、iOS 7 がUITextView.scrollRangeToVisible:最初の呼び出しの範囲 {1050,1} で呼び出します。これは私には意味がありません。_ensureselectionvisibleこの内部 API は、スクロール イベントを呼び出すために使用した可能性のある関数へのトレースバックなしで呼び出されるため、これは内部的に呼び出されており、コードからではありません。

おそらく、最初の 2 つの呼び出しは単に文字を選択するためのものであり、その後、内部的に deleteBackwards を呼び出します。それは理にかなっている。その後、私は盲目的に飛んでいます。なぜスクロールするのか、なぜscrollRangeToVisible:間違った範囲で呼び出すのかわかりません。

ユーザーがテキストを編集しているときにUITextView.scrollRangeToVisible:呼び出しなしで返すことでオーバーライドすることで、問題をある程度軽減することができました。[super scrollRangeToVisible]これを行うには、スクロールビュー デリゲート呼び出しの一部をオーバーライドし、スクロールが発生しないことを示すフラグを設定します。これは、私にとっては非常に醜いハックであり、特定の状況で問題が発生します。たとえば、最初の編集時にビューをタップしたときや、スクロール ビューの減速が停止したときなどです。

要するに、スクロール イベントが発生し (おそらく前の行が上に移動したため)、何らかの理由で iOS がカーソルがビュー内にないと判断したということです。さらに興味深いのは、間違った範囲が指定されていても、正しい位置が指定されているため、ビュー内の間違った位置にスクロールされることです。さらに興味深いのは、特定の条件でのみ発生し、その条件が発生した 2 番目のインスタンスでのみ発生することです。私が考えることができる唯一のことは、テキストが削除された後に UITextView の高さが正しく計算されず、テキストが実際とは異なる場所にあると信じていることです。

前もって感謝します!

アップデート:

先ほど話したフラグを設定しましたが、scrollViewDidScroll:他のいくつかのインスタンスでのジャンプを妨げません。ただし、テキストを逆方向に削除しても画面がジャンプする場合があります。ビューがまだジャンプするインスタンスでさらに奇妙なのは、[super scrollRangeToVisible]呼び出されないようにしていることです。そのため、これに加えて内部で何かが起こっており、ビューがスクロールされています。

アップデート:

613pts から 670pts の間で上方にジャンプしているように見えます。点数差の原因がわかりません。ジャンプする行数も異なります。条件間で何かが似ている必要があると思います。また、 が呼び出されたときに、この呼び出しとデリゲート呼び出しの間で が同じでscrollViewDidScroll:あることを確認しました。textView.selectedRange.locationtextViewDidChangeSelection:

4

2 に答える 2

0

scrollRangeToVisible:がトリガーされるため、 と のscrollViewDidScroll:両方を見ているUITextViewDelegateUIScrollViewDelegate、動作がおかしくなることがあります。setContentOffset:も同じようにします。これを防ぐには、 を設定する必要がありますscrollView.boundstextViewDidChangeSelection:私は代わりに使用しませんでし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テキストがやや長い場合、コードはキャレットを;の中央に配置します。IMHO、これはキャレットの上と下の両方のテキストのコンテキストをユーザーに提供するため、最良の動作です。

于 2015-06-25T18:21:48.033 に答える