UITextView прокрутка вверх после удаления / вставки текста

Я переписывал свое приложение для iOS 7. Теперь оно использует новый API TextKit. Я почти закончил, но есть еще одна проблема, которая вызывает у меня головную боль, так как я начал делать переход.

Проблема: Когда я вводю текст в UITextView, иногда он прокручивается вверх, скрывая курсор. Сложно точно определить шаблон, но, похоже, это происходит после того, как я удалю строку, и текст ниже должен сдвинуться вверх. Но только ВТОРОЙ раз я удаляю строку и ТОЛЬКО с определенными строками в документе (обычно это строки, которые имеют только символ новой строки, но не всегда...)

Я попытался использовать эти решения на stackru:

Как остановить прокрутку UITextView при входе

UITextView продолжает прокручиваться после вставки каждой новой строки, поэтому строки не видны (iPhone OS)

Прокрутите вниз UITextView с ошибками в iOS 7

Есть также много других проблем, когда прокрутка не работает правильно, когда UITableView внутри UITextView. У меня нет табличных представлений на мой взгляд.

Я хочу отметить, что я создал подкласс UITextView и обнаружил, что это определенно iOS 7, делающая вызовы, чтобы не только прокручивать представление вверх. Кроме того, что еще более странно, это то, что когда вы нажимаете кнопку "назад" (чтобы удалить символ из UITextView), он отправляет два сообщения textViewDidChangeSelection:, Вот стек вызовов при возникновении проблемы. (Когда проблема не возникает, я все еще получаю два вызова делегировать textViewDidChangeSelection:... что кажется нормальным)

  • UITextView делегат textViewDidChangeSelection: содержит позицию удаляемого символа длиной один. (т.е. 1050,1)
  • UITextView делегат textViewDidChangeSelection: вызывается снова и содержит ту же позицию, но с нулевой длиной. (т.е. 1050,0)
  • scrollViewDidScroll (до 3 раз. но обычно только один раз)
  • Тогда iOS 7 звонит UITextView.scrollRangeToVisible: w/ диапазон ПЕРВОГО вызова {1050,1}. Это не имеет смысла для меня. Я знаю, что это вызывается изнутри, а не из любого моего кода, потому что этот внутренний API называется _ensureselectionvisible без какой-либо трассировки к функции, которую я мог использовать для вызова события прокрутки.

Предположительно, первые два вызова просто выбирают символ, а затем внутренне вызывают deleteBackwards. В этом есть смысл. После этого я летаю вслепую. Я понятия не имею, почему это прокручивает или почему это вызывает scrollRangeToVisible: W / НЕПРАВИЛЬНЫЙ диапазон.

Я был в состоянии смягчить проблему несколько перебегая UITextView.scrollRangeToVisible: возвращая без звонка [super scrollRangeToVisible] когда пользователь редактирует текст. Я делаю это, переопределяя некоторые вызовы делегатов scrollview и устанавливая флаг, который говорит, что прокрутка не может происходить. Для меня это огромный уродливый хак, и проблема все еще возникает в определенных ситуациях - например, когда я нажимаю на представление при первом редактировании, а иногда, когда представление с прокруткой перестает замедляться.

Короче говоря, происходит то, что происходит событие прокрутки (вероятно, из-за перемещения предыдущей строки), и iOS почему-то считает, что курсор не находится в представлении. Что еще более любопытно, это то, что, хотя и предоставлен неправильный диапазон, он обеспечивает правильное положение, которое, в свою очередь, прокручивает его в неправильном месте в представлении. Еще более любопытно, что это происходит только в определенных условиях и только во втором случае, когда это состояние возникает. Единственное, о чем я могу думать, это то, что высота UITextView вычисляется неправильно после удаления текста, и он считает, что текст находится в другом месте, чем он есть на самом деле.

Заранее спасибо!

ОБНОВИТЬ:

Я установил флаг, о котором говорил ранее scrollViewDidScroll: и это не мешает прыжкам в некоторых других случаях. Тем не менее, все еще есть случаи, когда удаление текста в обратном направлении по-прежнему перемещается по экрану. Что еще более странно в случае, когда вид все еще скачет, это то, что я вижу, что я предотвращаю [super scrollRangeToVisible] от вызова! Так что что-то происходит внутри, ДОПОЛНИТЕЛЬНО, к этому, что заставляет представление прокручиваться.

ОБНОВИТЬ:

Кажется, он прыгает между 613 и 670 пунктами вверх. Я не уверен, что вызывает разницу в баллах. Количество линий, которые он прыгает, также варьируется. Я подозреваю, что между условиями должно быть что- то похожее. Кроме того, когда scrollViewDidScroll: мне звонят, я проверил, что textView.selectedRange.location то же самое между этим вызовом и делегатом textViewDidChangeSelection: вызов.

2 ответа

Решение

Это ошибка в последних версиях iOS. Надеемся, что это будет исправлено в ближайшее время, до тех пор, насколько я знаю, единственный вариант - вручную сказать UITextView, чтобы прокручивать позицию курсора всякий раз, когда выбор изменяется:

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

Это работает для меня, но, возможно, ошибка немного отличается для вашего приложения. Не уверен, что вы должны делать... если вы не можете решить это, я бы подал TSI в Apple. https://developer.apple.com/support/technical/submit/

Поскольку scrollRangeToVisible: триггеры scrollViewDidScroll:, если вы смотрите оба UITextViewDelegate а также 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; ИМХО, это лучшее поведение, потому что оно дает пользователю контекст текста как над, так и под кареткой.

Другие вопросы по тегам