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

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

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

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

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

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

Прокрутите вниз UITextView неустойчиво в iOS 7 < / а>

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

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

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

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

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

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

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

ОБНОВИТЬ:

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

ОБНОВИТЬ:

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


person PeqNP    schedule 19.01.2014    source источник


Ответы (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/

person Abhi Beckert    schedule 19.01.2014
comment
К сожалению, это не решает мою проблему. Но спасибо за ответ! - person PeqNP; 20.01.2014
comment
Я ошибался! Это решило мою проблему. У меня все еще была какая-то логика, когда я предотвращал прокрутку в определенных условиях. Большое спасибо! - person PeqNP; 20.01.2014

Поскольку 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; IMHO, это лучшее поведение, потому что оно дает пользователю контекст текста как над, так и под кареткой.

person mikeho    schedule 25.06.2015