Остановить UITextView от прыжков при программной установке текста

Мне нужно обновить небольшой объем текста в прокручивающемся UITextView. Я буду вставлять только символ там, где сейчас находится курсор, и буду делать это нажатием кнопки на панели навигации.

Моя проблема в том, что всякий раз, когда я вызываю метод setText текстового представления, он переходит в конец текста. Я пробовал использовать contentOffset и сбросить selectedRange, но это не сработало! Вот мой пример:

// Remember offset and selection
CGPoint contentOffset = [entryTextView contentOffset];
NSRange selectedRange = [entryTextView selectedRange];
// Update text
entryTextView.text = entryTextView.text;
// Try and reset offset and selection
[entryTextView setContentOffset:contentOffset animated:NO];
[entryTextView setSelectedRange: selectedRange];

Есть ли способ обновить текст вообще без движения прокрутки ... как если бы они только что набрали что-то на клавиатуре?

Редактировать:

Я пробовал использовать метод делегата textViewDidChange:, но он все еще не прокручивается до исходного местоположения.

- (void)textViewDidChange:(UITextView *)textView {
    if (self.programChanged) {
        [textView setSelectedRange:self.selectedRange];
        [textView setContentOffset:self.contentOffset animated:NO];
        self.programChanged = NO;
    }
}

- (void)changeButtonPressed:(id)sender {
    // Remember position
    self.programChanged = YES;
    self.contentOffset = [entryTextView contentOffset];
    self.selectedRange = [entryTextView selectedRange];
    // Update text
    entryTextView.text = entryTextView.text;
}

person Michael Waterfall    schedule 14.05.2009    source источник


Ответы (13)


Если вы используете iPhone 3.0 или новее, вы можете решить эту проблему:

textView.scrollEnabled = NO;

//You should know where the cursor will be(if you update your text by appending/inserting/deleting you can know the selected range) so keep it in a NSRange variable.

Then update text:
textView.text = yourText;

textView.scrollEnabled = YES;
textView.selectedRange = range;//you keep before

Теперь он должен работать (больше никаких прыжков)

С уважением, Меир Ассаяг

person Mayosse    schedule 03.05.2010

Основываясь на предложении Меира, вот код, который удаляет выделение программно (да, я знаю, что есть кнопка меню выбора, которая тоже делает это, но я делаю что-то немного странное) без прокрутки текстового представления.

NSRange selectedRange = textView.selectedRange;
textView.scrollEnabled = NO;
// I'm deleting text. Replace this line with whatever insertions/changes you want
textView.text = [textView.text
                stringByReplacingCharactersInRange:selectedRange withString:@""];
selectedRange.length = 0;
// If you're inserting text, you might want to increment selectedRange.location to be
// after the text you inserted
textView.selectedRange = selectedRange;
textView.scrollEnabled = YES;
person Jacques    schedule 15.08.2010
comment
Это решение (установка scrollEnabled ПОСЛЕ установки выбранного диапазона, но не раньше) устраняет проблему с прыгающим текстом, которая возникала при установке свойства attributedText для UITextView! Спасибо - person software evolved; 22.05.2013

Это решение работает для iOS 8:

let offset = textView.contentOffset
textView.text = newText
textView.layoutIfNeeded()
textView.setContentOffset(offset, animated: false)

Необходимо вызвать ровно setContentOffset:animated:, потому что только это отменит анимацию. textView.contentOffset = offset не отменяет анимацию и не помогает.

person Anton Zherdev    schedule 29.01.2015

Следующие два решения не работают для меня на iOS 8.0.

textView.scrollEnabled = NO;
[textView.setText: text];
textView.scrollEnabled = YES;

и

CGPoint offset = textView.contentOffset;
[textView.setText: text];
[textView setContentOffset:offset];

Я установил делегата в текстовое представление для отслеживания события прокрутки и заметил, что после моей операции по восстановлению смещения смещение снова сбрасывается до 0. Поэтому я вместо этого использую основную очередь операций, чтобы убедиться, что моя операция восстановления выполняется после параметра «сбросить в 0».

Вот мое решение, которое работает для iOS 8.0.

CGPoint offset = self.textView.contentOffset;
self.textView.attributedText = replace;
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
    [self.textView setContentOffset: offset];
}];
person Harper    schedule 07.01.2015
comment
Итак, это устранило мою проблему при тестировании в симуляторе, но не устранило проблему при запуске на моем устройстве. Это заставляет меня думать, что это все еще состояние гонки. Я рассматриваю возможность создания подкласса uitextview и переопределения метода Layoutsubviews, чтобы он не запускался, когда у меня включено свойство блокировки. - person Lobsterman; 07.04.2015

Ни одно из предложенных решений не помогло мне. -setContentOffset: animated: запускается -setText: 3 раза с анимацией YES и contentOffset в конце (за вычетом поля по умолчанию 8pt для UITextView). Я завернул -setText: в охранник:

textView.contentOffsetAnimatedCallsDisabled = YES;
textView.text = text;
textView.contentOffsetAnimatedCallsDisabled = NO;

В подклассе UITextView в -setContentOffset: animated: put

if (contentOffsetAnimatedCallsDisabled) return; // early return

среди другой вашей логики. Не забывайте суперпризыв. Это работает.

Рафаэль

person Raphael Schaad    schedule 20.07.2010
comment
Спасибо за предложение, попробую! - person Michael Waterfall; 21.07.2010
comment
Это не частный API, вы переопределяете UITextView и реализуете поведение самостоятельно в соответствии с моим примером. Ответ Меира Ассаяга не сработал в моем случае и на iOS 3.2. - person Raphael Schaad; 20.08.2010
comment
В iOS 7 это не работает, поскольку метод contentOffset будет вызываться снова во время отображения. Ищу рабочее решение. - person phatmann; 22.09.2013

Чтобы отредактировать текст UITextView, вам необходимо обновить его поле textStorage:

[_textView.textStorage beginEditing];

NSRange replace = NSMakeRange(10, 2); //enter your editing range
[_textView.textStorage replaceCharactersInRange:replace withString:@"ha ha$ "];

//if you want to edit the attributes
NSRange attributeRange = NSMakeRange(10, 5); //enter your editing attribute range
[_textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[UIColor greenColor] range:attributeRange];

[_textView.textStorage endEditing];

Удачи

person Yedidya Reiss    schedule 08.02.2015

в iOS 7. Есть швы, чтобы быть ошибкой с sizeThatFits и иметь разрывы строк в вашем UITextView, решение, которое, как я обнаружил, работает, состоит в том, чтобы обернуть его, отключив прокрутку. Нравится:

textView.scrollEnabled = NO;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
textView.scrollEnabled = YES;

и странные прыжки были исправлены.

person over_optimistic    schedule 25.04.2014
comment
В iOS 8 настройка textView.scrollEnabled = NO в textDidChange:, похоже, навсегда отключает прокрутку. Однако в этой ОС кажется достаточно просто переключить scrollEnabled один раз во время viewDidLoad, т.е. установить его на NO и сразу вернуться к YES - person Alex Hoppen; 01.09.2015

Я столкнулся с похожей, если не такой, проблемой в IOS9. Изменение характеристик некоторого текста, скажем, на ЖИРНЫЙ, заставило представление прокручивать выделение вне поля зрения. Я отсортировал это, добавив вызов scrollRangeToVisible после setSelectedRange:

    [self setSelectedRange:range];
    [self scrollRangeToVisible:range];
person easiwriter    schedule 19.06.2016
comment
Работает отлично, без ощутимого прыжка и так просто! Не знаю, как я это пропустил! - person siburb; 26.04.2017

Взгляните на UITextViewDelegate, я считаю, что метод textViewDidChangeSelection может позволить вам делать то, что вам нужно.

person drewh    schedule 14.05.2009
comment
Спасибо за ваш вклад. Что должно произойти в этом методе делегата? Это когда должен произойти окончательный сброс contentOffset и selectedRange? - person Michael Waterfall; 14.05.2009

Старый вопрос, но у меня была такая же проблема с приложением iOS 7. Требуется немного изменить contentOffset после цикла выполнения. Вот небольшая идея.

self.clueString = [self.level clueText];
CGPoint point = self.clueText.contentOffset;
self.clueText.attributedText = self.clueString;
double delayInSeconds = 0.001; // after the run loop update
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self.clueText setContentOffset:point animated:NO]; 
});
person NixonsBack    schedule 06.02.2014

Наконец попробуйте это, проверил на iOS 10

let offset = textView.contentOffset
textView.attributedText = newValue
OperationQueue.main.addOperation {
    self.textView.setContentOffset(offset, animated: false)
}
person Petr Syrov    schedule 02.09.2017

Не такое элегантное решение, но оно работает, так что кого это волнует:

- (IBAction)changeTextProgrammaticaly{
     myTextView.text = @"Some text";
     [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(rewindOffset) userInfo:nil repeats:NO];
}

- (void)rewindOffset{
    [myTextView setContentOffset:CGPointMake(0,0) animated: NO];
}
person Tiger    schedule 03.02.2010
comment
(Это решение появилось после того, как все другие предлагаемые здесь решения не сработали для меня) - person Tiger; 03.02.2010
comment
Это не заслуживает отрицательного голоса. Иногда подобные проблемы с прокруткой необходимо решать в цикле выполнения. Я знаю, что могут быть более элегантные решения, но это работает и безвредно. - person phatmann; 21.09.2013

Я нашел решение, которое надежно работает в iOS 6 и 7 (и, возможно, более ранних версиях). В подклассе UITextView сделайте следующее:

@interface MyTextView ()
@property (nonatomic) BOOL needToResetScrollPosition;
@end

@implementation MyTextView

- (void)setText:(NSString *)text
{
    [super setText:text];
    self.needToResetScrollPosition = YES;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.needToResetScrollPosition) {
        self.contentOffset = CGPointMake(0, 0);
        self.needToResetScrollPosition = NO;
    }
}

Ни один из других ответов не работает в iOS 7, потому что он будет регулировать смещение прокрутки во время отображения.

person phatmann    schedule 21.09.2013
comment
self.contentOffset для меня всегда 0,0 (этот подкласс не влияет на предотвращение прокрутки в iOS 7). - person Michael; 11.03.2014