Как заставить UITextView прокручиваться вверх каждый раз, когда я меняю текст?


100

Хорошо, у меня проблема с файлом UITextView. Вот в чем проблема:

Я добавляю текст в файл UITextView. Затем пользователь дважды щелкает, чтобы выбрать что-нибудь. Затем я изменяю текст в UITextView(программно, как указано выше), и UITextView прокручиваю до конца страницы, где есть курсор.

Однако это НЕ то место, где пользователь щелкнул. Он ВСЕГДА прокручивается до конца UITextViewнезависимо от того, где щелкнул пользователь.

Итак, вот мой вопрос: как заставить UITextViewпрокрутку вверх каждый раз, когда я меняю текст? Я пробовал contentOffsetи scrollRangeToVisible. Ни работать.

Мы ценим любые предложения.

Ответы:


149
UITextView*note;
[note setContentOffset:CGPointZero animated:YES];

Это делает это за меня.

Версия Swift (Swift 4.1 с iOS 11 на Xcode 9.3):

note.setContentOffset(.zero, animated: true)

13
Мне это не поможет.
Дмитрий

6
Это работает, но зачем это вообще нужно? Логика прокрутки Apple нуждается в доработке, слишком много прыжков через обруч, чтобы заставить вещи вести себя так, как они должны автоматически в большинстве случаев.
Джон Контарино

4
У меня работает в iOS 9, но не ранее viewDidAppear().
Мюррей Сагал

4
Вы можете вызвать это раньше, чем viewDidAppear (), вызвав его в viewDidLayoutSubviews ()
аркадий боб

Я добавил текстовое представление в ячейку таблицы, поэтому я устанавливаю это в ячейке для строки, но не работаю
Чандни

67

Если кто имеет эту проблему прошивки 8, я обнаружил , что просто установив UITextView'sсвойство текста, то вызов scrollRangeToVisibleс NSRangeс location:0, length:0работал. Мое текстовое представление было недоступно для редактирования, и я протестировал как возможность выбора, так и отсутствие возможности выбора (ни один параметр не повлиял на результат). Вот пример Swift:

myTextView.text = "Text that is long enough to scroll"
myTextView.scrollRangeToVisible(NSRange(location:0, length:0))

спасибо, ничего из вышеперечисленного у меня не сработало. Ваш образец сделал.
user1244109

Это работает, но мы получаем анимацию прокрутки, которую нужно отключить, я предпочитаю отключать прокрутку и повторно включать ее, как предложил
@miguel

Цель-C: [myTextView scrollRangeToVisible: NSMakeRange (0, 0)];
Stateful

Кажется, это больше не работает. iOS 9 и Xcode 7.3.1: /
wasimsandhu

Swift 3 Stll работает! :) просто скопируйте эту строку в свой метод UITextFielDelegate. `func textViewDidEndEditing (_ textView: UITextView) {textView.scrollRangeToVisible (NSRange (location: 0, length: 0))}`
lifewithelliott

46

И вот мое решение ...

    override func viewDidLoad() {
        textView.scrollEnabled = false
        textView.text = "your text"
    }

    override func viewDidAppear(animated: Bool) {
        textView.scrollEnabled = true
    }

5
Мне не нравится это решение, но, по крайней мере, в iOS 9 beta 5 это единственное, что у меня работает. Я предполагаю, что большинство других решений предполагают, что ваше текстовое представление уже видно. В моем случае это не всегда так, поэтому мне приходится отключать прокрутку до тех пор, пока она не появится.
robotspacer

1
Это было единственное, что у меня тоже сработало. iOS 9 с использованием нередактируемого текстового представления.
SeanR

1
Это работает для меня, iOS 8, не редактируемое текстовое представление с приписанной строкой. В viewWillAppear - не работает
Тина Ж

Я согласен с @robotspacer. и это решение до сих пор единственное, что мне подходит.
lerp90

Для меня это решение работало только с короткими (атрибутированными) текстами. При наличии более длинных текстов ошибка прокрутки UITextView продолжает оставаться в живых :-) Мое (немного уродливое) решение заключалось в использовании dispatch_after с 2-секундной задержкой для повторного включения прокрутки. UITextView, похоже, нужно немного времени, чтобы сделать свой текстовый макет ...
LaborEtArs,

38

Вызов

scrollRangeToVisible(NSMakeRange(0, 0))

работает, но позвони

override func viewDidAppear(animated: Bool)

4
Я подтверждаю, что у меня это сработало только в viewDidAppear.
Эрик Ф.

2
Это единственный, который мне подходит. Спасибо. Но как отключить анимацию, которая меня немного раздражает?
rockhammer 04

Хотя это работает, решение I @Maria лучше, потому что оно не показывает краткого «скачка».
Сипка Schoorstra

1
Беру свой последний комментарий; это лучший ответ для меня, так как он работает как на iOS 10, так и на iOS 11, тогда как ответ
Сипке

@SipkeSchoorstra, см. Мой ответ ниже, чтобы он работал без скачков (с использованием KVO).
Терри

29

Я использовал attributedString в HTML с недоступным для редактирования текстовым представлением. Установка смещения контента у меня тоже не сработала. Это сработало для меня: отключите прокрутку, установите текст, а затем снова включите прокрутку

[yourTextView setScrollEnabled:NO];
yourTextView.text = yourtext;
[yourTextView setScrollEnabled:YES];

У меня был textView внутри tableViewCell. В этом случае textView прокручивается до прибл. 50%. Это решение настолько просто, насколько логично. Спасибо
Джулиан Ф. Вайнерт

1
@ JulianF.Weinert Похоже, это не работает для iOS10. У меня такая же ситуация, как у вас, с textView в tableViewCell. Я не использую возможности редактирования textView. Мне просто нужен прокручиваемый текст. Но textView всегда прокручивается примерно до 50%, как вы сказали. Я также пробовал все другие предложения по установке setContentOffset и scrollRangeToVisible в этом сообщении. Ни одной работы. Вы могли сделать что-нибудь еще, чтобы заставить эту работу работать?
JeffB6688

@ JeffB6688 извини, нет. Это действительно очень давно ... Может быть, это еще одна причуда «нового» релиза iOS?
Джулиан Ф. Вайнерт

Но, к сожалению, не идеально на iOS 11.2. Ответ работа Onlu @RyanTCB «s на оба прошивке 10 и 11. прошивки
Сипке Schoorstra

Пока что у меня это отлично работает на iOS 9, 10 и 11, но мне пришлось включить его viewDidAppearкак часть более широкого серьезного исправления
MadProgrammer

19

Поскольку ни одно из этих решений у меня не сработало, и я потратил слишком много времени, собирая решения по кусочкам, это наконец решило проблему для меня.

override func viewWillAppear(animated: Bool) {
    dispatch_async(dispatch_get_main_queue(), {
        let desiredOffset = CGPoint(x: 0, y: -self.textView.contentInset.top)
        self.textView.setContentOffset(desiredOffset, animated: false)
    })
}

Это действительно глупо, что это не поведение по умолчанию для этого элемента управления.

Надеюсь, это поможет кому-то другому.


2
Это было очень полезно! Мне также пришлось добавить: self.automaticallyAdjustsScrollViewInsets = NO; в представление, содержащее UITextView, чтобы заставить его полностью работать.
jengelsma

Я заставил его работать только в SWIFT 3, с другим расчетом смещения self.textView.contentOffset.yна основе этого ответа: stackoverflow.com/a/25366126/1736679
Эфрен

Сработало для меня, когда я адаптировал последний синтаксис Swift 3
hawmack13,

полностью согласен, насколько это безумие? спасибо за решение
ajonno 07

18

Я не уверен, понял ли я ваш вопрос, но вы пытаетесь просто прокрутить представление наверх? Если да, тебе следует сделать

[textview scrollRectToVisible:CGRectMake(0,0,1,1) animated:YES];


1
@SamStewart Ссылка кажется неработающей, всегда ведет на страницу моих учетных записей (уже авторизованных), можете ли вы предоставить дополнительную информацию о том, какое решение они там предлагают?
uliwitness

@uliwitness Это очень старая информация, я бы порекомендовал попробовать более современный ресурс вместо 8-летней давности. API iOS кардинально изменился за эти 8 лет.
Benjamin Sussman

1
Я пытался найти более новую информацию. Если вы знаете, где я могу его найти, я был бы признателен. По-прежнему, похоже, та же проблема :(
uliwitness

Спасибо, это сработало для меня, потому что я также установил вставки в определяемых пользователем атрибутах времени выполненияTextView.scrollRectToVisible(CGRect(x: 0,y: 0,width: 1,height: 1), animated: false)
Курт Лейн

13

Для iOS 10 и Swift 3 я сделал:

override func viewDidLoad() {
    super.viewDidLoad()
    textview.isScrollEnabled = false
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    textView.isScrollEnabled = true
}

Сработало для меня, не уверен, что у вас проблема с последней версией Swift и iOS.


Идеальное решение для подражания!
iChirag

У меня проблема с прокруткой с UITextView в UIView, управляемом UIPageViewController. (На этих страницах показаны мои экраны «Справка» / «О программе».) Ваше решение работает для всех страниц, кроме первой. Перелистывание на любую страницу и обратно на первую страницу фиксирует положение прокрутки этой первой страницы. Я попытался добавить еще один .isScrollEnabled = False в viewWillAppear, но это не повлияло. Я не понимаю, почему первая страница ведет себя иначе, чем остальные.
Уэйн Хендерсон

[обновление] Просто решил! Введение задержки в 0,5 секунды перед триггерами .isScrollEnabled = true устраняет проблему первой загрузки.
Уэйн Хендерсон

очень хорошо . . .
Maysam R

Работал как шарм. Единственное решение, которое у меня работает. Ты человек.
Ленивый ниндзя,

10

У меня есть обновленный ответ, в котором текстовое представление будет отображаться правильно и без анимации прокрутки у пользователя.

override func viewWillAppear(animated: Bool) {
    dispatch_async(dispatch_get_main_queue(), {
        self.introductionText.scrollRangeToVisible(NSMakeRange(0, 0))
    })

Почему вы делаете в основной очереди, синус viewWillAppear работает в основной очереди?
karthikPrabhu Alagu

1
Слишком много лун назад для меня вспомнить , но его имя здесь: stackoverflow.com/a/17490329/4750649
Macinnis

8

Вот как это работало в выпуске iOS 9, так как textView прокручивается сверху, прежде чем появится на экране.

- (void)viewDidLoad
{
    [super viewDidLoad];
    textView.scrollEnabled = NO;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    textView.scrollEnabled = YES;
}

1
Это сработало. Между прочим, это научило меня кое-чему очень важному: «view» в «viewwillappear» относится не только к self.view, но, по-видимому, ко ВСЕМ представлениям. Нет?
Sjakelien

8

Вот как я это сделал

Swift 4:

extension UITextView {

    override open func draw(_ rect: CGRect)
    {
        super.draw(rect)
        setContentOffset(CGPoint.zero, animated: false)
    }

 }

Это единственный метод, который дает мне наилучшие результаты. Я поддерживаю.
smoothBlue

7

Это сработало для меня

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    DispatchQueue.main.async {
      self.textView.scrollRangeToVisible(NSRange(location: 0, length: 0))
    }
  }

Это очень странно. На большинстве устройств перенос вызова DispatchQueue.main.async(...не требуется, но по какой-то причине (например, на iPhone 5s (iOS 12.1)) он не работает, если вы не отправите вызов . Но, как сообщает стек вызовов отладчика, во всех случаях viewWillAppear() уже выполняется основная очередь ... - WTF, Apple ???
Николас Миари

2
Мне также было интересно, почему я должен отправлять явно в основной очереди, даже если я запускаю все события в основной очереди. Но у меня это сработало
Анкит Гарг

6

Попробуйте переместить курсор в верхнюю часть текста.

NSRange r  = {0,0};
[yourTextView setSelectedRange:r];

Посмотрите, как это происходит. Убедитесь, что вы вызываете это после того, как все ваши события активированы или все, что вы делаете, будет сделано.


попробовал человек, не повезло. Вроде что-то с firstResponder. Есть другие предложения?
Сэм Стюарт,

у меня такая же проблема с позицией курсора, у меня это сработало ... отлично +1 для этого.
Дхавал

У меня есть анимация снизу вверх, но мне это не требовалось, так как это удалить.
Dhaval

2
Он не прокручивается вверх (меньше на несколько пикселей).
Дмитрий

6

Моя проблема заключается в том, чтобы установить textView для прокрутки вверх при появлении представления. Для iOS 10.3.2 и Swift 3.

Решение 1:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // It doesn't work. Very strange.
    self.textView.setContentOffset(CGPoint.zero, animated: false)
}
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // It works, but with an animation effection.
    self.textView.setContentOffset(CGPoint.zero, animated: false)
}

Решение 2:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // It works.       
    self.textView.contentOffset = CGPoint.zero
}

5

Объединил предыдущие ответы, теперь он должен работать:

talePageText.scrollEnabled = false
talePageText.textColor = UIColor.blackColor()
talePageText.font = UIFont(name: "Bradley Hand", size: 24.0)
talePageText.contentOffset = CGPointZero
talePageText.scrollRangeToVisible(NSRange(location:0, length:0))
talePageText.scrollEnabled = true

lmao единственное, что у меня работало как глупо. Однако я смог удалить scrollRangeToVisible.
Jordan H

Вам нужно будет заменить NSRange (0,0) на NSMakeRange (0,0)
RobP


5

просто вы можете использовать этот код

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    textView.scrollRangeToVisible(NSMakeRange(0, 0))
}

4

Swift 2 ответ:

textView.scrollEnabled = false

/* Set the content of your textView here */

textView.scrollEnabled = true

Это предотвращает прокрутку textView до конца текста после его установки.


4

В Swift 3:

  • viewWillLayoutSubviews - это место для внесения изменений. viewWillAppear также должен работать, но по логике макет должен выполняться в viewWillLayoutSubviews .

  • Что касается методов, то будут работать как scrollRangeToVisible, так и setContentOffset . Однако setContentOffset позволяет включать или отключать анимацию.

Предположим, что UITextView назван yourUITextView . Вот код:

// Connects to the TextView in the interface builder.
@IBOutlet weak var yourUITextView: UITextView!

/// Overrides the viewWillLayoutSubviews of the super class.
override func viewWillLayoutSubviews() {

    // Ensures the super class is happy.
    super.viewWillLayoutSubviews()

    // Option 1:
    // Scrolls to a range of text, (0,0) in this very case, animated.
    yourUITextView.scrollRangeToVisible(NSRange(location: 0, length: 0))

    // Option 2:
    // Sets the offset directly, optional animation.
    yourUITextView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
}

viewWillLayoutSubviews вызывается при любом изменении размера представления. Обычно это не то, что вам нужно.
uliwitness

4

Мне пришлось вызвать следующее внутри viewDidLayoutSubviews (вызывать внутри viewDidLoad было слишком рано):

    myTextView.setContentOffset(.zero, animated: true)

3

Это сработало для меня. Он основан на ответе RyanTCBs, но это вариант того же решения для Objective-C:

- (void) viewWillAppear:(BOOL)animated {
    // For some reason the text view, which is a scroll view, did scroll to the end of the text which seems to hide the imprint etc at the beginning of the text.
    // On some devices it is not obvious that there is more text when the user scrolls up.
    // Therefore we need to scroll textView to the top.
    [self.textView scrollRangeToVisible:NSMakeRange(0, 0)];
}

3
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [textview setContentOffset:CGPointZero animated:NO];
}

viewWillLayoutSubviews вызывается при любом изменении размера представления. Обычно это не то, что вам нужно.
uliwitness

3
-(void) viewDidAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}

- (void)viewWillAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}
  • ViewWillappearсделает это мгновенно, прежде чем пользователь заметит, но ViewDidDisappearраздел будет лениво анимировать.
  • [mytextView setContentOffset:CGPointZero animated:YES]; иногда НЕ работает (я не знаю почему), поэтому лучше используйте scrollrangetovisible.

3

Проблема решена: iOS = 10.2 Swift 3 UITextView

Я просто использовал следующую строку:

displayText.contentOffset.y = 0

1
Работает (iOS 10.3)! Я добавил его внутрь, viewDidLayoutSubviewsчтобы это происходило при повороте экрана. В противном случае смещение содержимого станет смещенным.
Pup

2
[self.textView scrollRectToVisible:CGRectMake(0, 0, 320, self.textView.frame.size.height) animated:NO];

Пожалуйста, отредактируйте с более подробной информацией.
Schemetrical

2

У меня была проблема, но в моем случае текстовое представление является подвидом некоторого настраиваемого представления и добавлено его в ViewController. Ни одно решение не помогло мне. Для решения проблемы добавлена ​​задержка 0,1 сек, а затем включена прокрутка. У меня это сработало.

textView.isScrollEnabled = false
..
textView.text = // set your text here

let dispatchTime = DispatchTime.now() + 0.1
DispatchQueue.main.asyncAfter(deadline: dispatchTime) {
   self.textView.isScrollEnabled = true
}

1

Для меня отлично работает этот код:

    textView.attributedText = newText //or textView.text = ...

    //this part of code scrolls to top
    textView.contentOffset.y = -64
    textView.scrollEnabled = false
    textView.layoutIfNeeded() //if don't work, try to delete this line
    textView.scrollEnabled = true

Для прокрутки до точного положения и отображения его в верхней части экрана я использую этот код:

    var scrollToLocation = 50 //<needed position>
    textView.contentOffset.y = textView.contentSize.height
    textView.scrollRangeToVisible(NSRange.init(location: scrollToLocation, length: 1))

Установка contentOffset.y прокручивает до конца текста, а затем scrollRangeToVisible прокручивает до значения scrollToLocation. Таким образом, нужная позиция появляется в первой строке scrollView.


1

Для меня в iOS 9 он перестал работать для меня с атрибутной строкой с методом scrollRangeToVisible (первая строка была с жирным шрифтом, другие строки были с обычным шрифтом)

Поэтому мне пришлось использовать:

textViewMain.attributedText = attributedString
textViewMain.scrollRangeToVisible(NSRange(location:0, length:0))
delay(0.0, closure: { 
                self.textViewMain.scrollRectToVisible(CGRectMake(0, 0, 10, 10), animated: false)
            })

где задержка:

func delay(delay:Double, closure:()->Void) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

1

Попробуйте использовать это двухстрочное решение:

view.layoutIfNeeded()
textView.contentOffset = CGPointMake(textView.contentInset.left, textView.contentInset.top)

Это сработало для меня. Мой UITextView находился внутри UICollectionVewCell. Я подозреваю, что scrollRangeToVisible прокручивается не в том месте, если макет не рассчитан должным образом
Джон Фаулер,

1

Вот мои коды. Работает нормально.

class MyViewController: ... 
{
  private offsetY: CGFloat = 0
  @IBOutlet weak var textView: UITextView!
  ...

  override viewWillAppear(...) 
  {
      ...
      offsetY = self.textView.contentOffset.y
      ...
  }
  ...
  func refreshView() {
      let offSetY = textView.contentOffset.y
      self.textView.setContentOffset(CGPoint(x:0, y:0), animated: true)
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
        [unowned self] in
        self.textView.setContentOffset(CGPoint(x:0, y:self.offSetY), 
           animated: true)
        self.textView.setNeedsDisplay()
      }
  }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.