¿Cómo fuerzo a un UITextView a desplazarse hacia la parte superior cada vez que cambio el texto?

99

De acuerdo, tengo un problema con el UITextView. Aquí está el problema:

Agrego algo de texto a un UITextView. Luego, el usuario hace doble clic para seleccionar algo. Luego cambio el texto en UITextView(programáticamente como arriba) y los UITextView desplazamientos hacia la parte inferior de la página donde hay un cursor.

Sin embargo, ahí NO es donde el usuario hizo clic. SIEMPRE se desplaza hasta la parte inferior del, UITextViewindependientemente de dónde haya hecho clic el usuario.

Así que aquí está mi pregunta: ¿Cómo fuerzo el UITextViewdesplazamiento hacia la parte superior cada vez que cambio el texto? Lo he intentado contentOffsety scrollRangeToVisible. Ninguno de los dos funciona.

Cualquier sugerencia sera apreciada.

Sam Stewart
fuente

Respuestas:

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

Esto lo hace por mí.

Versión Swift (Swift 4.1 con iOS 11 en Xcode 9.3):

note.setContentOffset(.zero, animated: true)
Wayne Lo
fuente
13
No me ayudes.
Dmitry
6
Esto funciona, pero ¿por qué es necesario de todos modos? La lógica de desplazamiento de Apple necesita refinamiento, hay demasiados saltos de aro para que las cosas se comporten de la manera que deberían automáticamente en la mayoría de los casos.
John Contarino
4
Funciona para mí en iOS 9 pero no antes viewDidAppear().
Murray Sagal
4
Puede llamar a esto antes que viewDidAppear () llamándolo en viewDidLayoutSubviews ()
arcady bob
He agregado la vista de texto en la celda de la tabla, así que lo estoy configurando en la celda para la fila, pero no funciona
Chandni
67

Si alguien tiene este problema en iOS 8, me encontré con que sólo la creación de la UITextView'spropiedad de texto, a continuación, llamar scrollRangeToVisiblecon un NSRangecon location:0, length:0, trabajé. Mi vista de texto no era editable y probé tanto seleccionable como no seleccionable (ninguna configuración afectó el resultado). Aquí hay un ejemplo de Swift:

myTextView.text = "Text that is long enough to scroll"
myTextView.scrollRangeToVisible(NSRange(location:0, length:0))
nerd2know
fuente
gracias, nada de lo anterior funcionó para mí. Tu muestra lo hizo.
user1244109
Esto funciona, pero obtenemos una animación de desplazamiento que debe deshabilitarse, prefiero deshabilitar el desplazamiento y volver a habilitarlo como sugirió
@miguel
Objective-C: [myTextView scrollRangeToVisible: NSMakeRange (0, 0)];
estado
Esto parece que ya no funciona. iOS 9 y Xcode 7.3.1: /
wasimsandhu
¡Swift 3 Stll funciona! :) simplemente copie esta línea en su método UITextFielDelegate. `func textViewDidEndEditing (_ textView: UITextView) {textView.scrollRangeToVisible (NSRange (ubicación: 0, longitud: 0))}`
lifewithelliott
46

Y aquí está mi solución ...

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

    override func viewDidAppear(animated: Bool) {
        textView.scrollEnabled = true
    }
miguel
fuente
5
No me encanta esta solución, pero al menos a partir de iOS 9 beta 5, es lo único que funciona para mí. Supongo que la mayoría de las otras soluciones suponen que su vista de texto ya está visible. En mi caso, eso no siempre es cierto, por lo que tengo que mantener el desplazamiento desactivado hasta que aparezca.
robotspacer
1
Esto fue lo único que funcionó para mí también. iOS 9, usando una vista de texto no editable.
SeanR
1
Esto funciona para mí, vista de texto no editable de iOS 8 con cadena atribuida. In viewWillAppear - no funciona
Tina Zh
Estoy de acuerdo con @robotspacer. y esta solución sigue siendo la única que me funciona.
lerp90
Para mí, esta solución solo funcionó con textos cortos (atribuidos). Cuando se tienen textos más largos, el error de desplazamiento de UITextView se mantiene vivo :-) Mi solución (un poco fea) fue utilizar dispatch_after con un retraso de 2 segundos para volver a habilitar el desplazamiento. UITextView parece necesitar una pequeña cantidad de tiempo para hacer su diseño de texto ...
LaborEtArs
38

Vocación

scrollRangeToVisible(NSMakeRange(0, 0))

funciona pero llámalo

override func viewDidAppear(animated: Bool)
RyanTCB
fuente
4
Confieso que solo funcionó para mí en viewDidAppear.
eric f.
2
Este es el único que me funciona. Gracias. Pero, ¿cómo desactivas la animación que encuentro un poco molesta?
rockhammer
Aunque esto funciona, la solución de I @Maria es mejor, porque esa no muestra un breve "salto".
Sipke Schoorstra
1
Retiro mi último comentario; esta es la mejor respuesta para mí, ya que funciona tanto en iOS 10 como en iOS 11, mientras que la respuesta de @ Maria no funciona bien en iOS 11.
Sipke Schoorstra
@SipkeSchoorstra, vea mi respuesta a continuación para que funcione sin saltos (usando KVO).
Terry
29

Estaba usando attributeString en HTML con la vista de texto no editable. Establecer el desplazamiento de contenido tampoco funcionó para mí. Esto funcionó para mí: deshabilite el desplazamiento habilitado, configure el texto y luego habilite el desplazamiento nuevamente

[yourTextView setScrollEnabled:NO];
yourTextView.text = yourtext;
[yourTextView setScrollEnabled:YES];
Maria
fuente
Tenía un textView dentro de un tableViewCell. En este caso, el textView se desplazó a aprox. 50%. Esta solución es tan simple como lógica. Gracias
Julian F. Weinert
1
@ JulianF.Weinert Esto no parece funcionar para iOS10. Tengo la misma situación que tú con textView en un tableViewCell. No estoy usando la capacidad de edición de textView. Solo quiero texto desplazable. Pero el textView siempre se desplaza aproximadamente al 50% como dijiste. También probé todas las otras sugerencias para configurar setContentOffset y scrollRangeToVisible en esta publicación. Ninguno funciona. ¿Hay algo más que pueda haber hecho para que esto funcione?
JeffB6688
@ JeffB6688 lo siento, no. Eso fue realmente hace mucho tiempo ... ¿Podría ser otra peculiaridad de un "nuevo" lanzamiento de iOS?
Julian F. Weinert
Pero no perfectamente en iOS 11.2, desafortunadamente. La respuesta de Onlu @RyanTCB funciona tanto en iOS 10 como en iOS 11.
Sipke Schoorstra
Esto funciona bien para mí hasta ahora en iOS 9, 10 y 11, pero tuve que habilitarlo viewDidAppearcomo parte de una serie más amplia de correcciones
MadProgrammer
19

Como ninguna de estas soluciones funcionó para mí y perdí demasiado tiempo juntando soluciones, esto finalmente me resolvió.

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)
    })
}

Es realmente una tontería que no sea el comportamiento predeterminado de este control.

Espero que esto ayude a alguien más.

Dibujó S.
fuente
2
¡Esto fue muy útil! También tuve que agregar: self.automaticallyAdjustsScrollViewInsets = NO; a la vista que contiene UITextView para que funcione completamente.
jengelsma
Solo lo hice funcionar en SWIFT 3, con otro cálculo de compensación self.textView.contentOffset.ybasado en esta respuesta: stackoverflow.com/a/25366126/1736679
Efren
Trabajado para mí una vez me he adaptado a la última sintaxis para Swift 3
hawmack13
totalmente de acuerdo en lo loco que es esto? gracias por la solución
ajonno
17

No estoy seguro de haber entendido su pregunta, pero ¿está intentando simplemente desplazar la vista hacia la parte superior? Si es así, deberías hacerlo

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

Benjamin Sussman
fuente
1
@SamStewart El enlace parece estar roto, siempre lleva a la página de mi cuenta (ya iniciada sesión). ¿Puede proporcionar más información sobre la solución que ofrecen allí?
uliwitness
@uliwitness Esta es información super antigua ahora, recomendaría probar un recurso más moderno en lugar de una publicación de 8 años. La API de iOS ha cambiado drásticamente en esos 8 años.
Benjamin Sussman
1
He intentado encontrar información más reciente. Si sabe dónde puedo encontrarlo, se lo agradecería. Todavía parece ser el mismo problema :(
uliwitness
13

Para iOS 10 y Swift 3 lo hice:

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

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

Funcionó para mí, no estoy seguro de si tiene el problema con la última versión de Swift e iOS.

ChallengerGuy
fuente
¡Una solución perfecta a seguir!
iChirag
Tengo el problema de desplazamiento con un UITextView en un UIView controlado por un UIPageViewController. (Estas páginas muestran mis pantallas de Ayuda / Acerca de). Su solución funciona para todas las páginas excepto la primera. Deslizar el dedo a cualquier página y volver a la primera página fija la posición de desplazamiento de esa primera página. Intenté agregar otro .isScrollEnabled = False en viewWillAppear pero esto no tuvo ningún efecto. No sé por qué la primera página se comporta de manera diferente al resto.
Wayne Henderson
[actualización] ¡Acabo de resolverlo! La introducción de un retraso de 0,5 segundos antes de que los disparadores .isScrollEnabled = true solucione el problema de la primera carga.
Wayne Henderson
muy agradable . . .
Maysam R
Trabajado como un encanto. La única solución que funciona para mí. Tu eres el hombre.
Lazy Ninja
10

Tengo una respuesta actualizada que hará que la vista de texto aparezca correctamente y sin que el usuario experimente una animación de desplazamiento.

override func viewWillAppear(animated: Bool) {
    dispatch_async(dispatch_get_main_queue(), {
        self.introductionText.scrollRangeToVisible(NSMakeRange(0, 0))
    })
MacInnis
fuente
¿Por qué estás haciendo en la cola principal, sine viewWillAppear se está ejecutando en la cola principal?
karthikPrabhu Alagu
1
Hace demasiadas lunas para recordar, pero se aborda aquí: stackoverflow.com/a/17490329/4750649
MacInnis
8

Así es como funcionó en iOS 9 Release, de modo que textView se desplaza en la parte superior antes de aparecer en la pantalla

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

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    textView.scrollEnabled = YES;
}
ageorgios
fuente
1
Esto hizo el truco. Por cierto, me enseñó algo muy importante: la 'vista' en 'viewwillappear' no solo se refiere a self.view, sino aparentemente a TODAS las vistas. ¿No?
Sjakelien
8

Asi es como lo hice

Rápido 4:

extension UITextView {

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

 }
Maksym Horbenko
fuente
Este es el único método que produce los mejores resultados para mí. Yo voto a favor.
smoothBlue
7

Esto funcionó para mi

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

    DispatchQueue.main.async {
      self.textView.scrollRangeToVisible(NSRange(location: 0, length: 0))
    }
  }
Ankit garg
fuente
Esto es muy raro. En la mayoría de los dispositivos, DispatchQueue.main.async(...no es necesario completar la llamada , pero por alguna razón, en (por ejemplo) iPhone 5s (iOS 12.1) no funciona a menos que lo envíe. Pero según lo informado por la pila de llamadas en el depurador, viewWillAppear() ya se está ejecutando en la cola principal en todos los casos ... - WTF, Apple ???
Nicolas Miari
2
También me preguntaba por qué tengo que enviar explícitamente en la cola principal a pesar de que estoy ejecutando todos los eventos en la cola principal. Pero esto funcionó para mí
Ankit garg
6

Intente esto para mover el cursor a la parte superior del texto.

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

Mira cómo va eso. Asegúrese de llamar a esto después de que se hayan activado todos sus eventos o de lo que esté haciendo.

John Ballinger
fuente
lo intenté hombre, sin suerte. Parece ser algo con el firstResponder. ¿Cualquier otra sugerencia?
Sam Stewart
tengo el mismo problema con la posición del cursor, funcionó para mí ... excelente +1 para eso.
Dhaval
Tengo la animación de abajo hacia arriba, pero no he requerido eso, así que cómo eliminarlo.
Dhaval
2
No se desplaza hacia la parte superior (menos en unos pocos píxeles).
Dmitry
6

Mi problema es configurar textView para que se desplace hacia la parte superior cuando apareció la vista. Para iOS 10.3.2 y Swift 3.

Solución 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)
}

Solución 2:

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

Combinando las respuestas anteriores, ahora debería funcionar:

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
Géza Mikló
fuente
Soy lo único que funcionó para mí, qué tontería. Sin scrollRangeToVisibleembargo, pude eliminar .
Jordan H
Deberá reemplazar NSRange (0,0) con NSMakeRange (0,0)
RobP
5
[txtView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

Esta línea de código me funciona.

Patricia
fuente
5

solo puedes usar este código

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

Respuesta rápida 2:

textView.scrollEnabled = false

/* Set the content of your textView here */

textView.scrollEnabled = true

Esto evita que textView se desplace hasta el final del texto después de configurarlo.

glaseado
fuente
4

En Swift 3:

  • viewWillLayoutSubviews es el lugar para realizar cambios. viewWillAppear también debería funcionar, pero lógicamente, el diseño debería realizarse en viewWillLayoutSubviews .

  • En cuanto a los métodos, tanto scrollRangeToVisible como setContentOffset funcionarán. Sin embargo, setContentOffset permite que la animación esté activada o desactivada.

Suponga que UITextView se denomina suUITextView . Aquí está el código:

// 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)
}
Marco Leong
fuente
viewWillLayoutSubviews se llama en cualquier cambio de tamaño de vista. Generalmente eso no es lo que quieres.
uliwitness
4

Tuve que llamar a la siguiente viewDidLayoutSubviews interior (llamar a viewDidLoad interior fue demasiado pronto):

    myTextView.setContentOffset(.zero, animated: true)
Charlie Seligman
fuente
no funciona en todos los escenarios
Raja Saad
3

Esto funcionó para mí. Se basa en la respuesta de RyanTCB, pero es la variante Objective-C de la misma solución:

- (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)];
}
Hermann Klecker
fuente
3
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [textview setContentOffset:CGPointZero animated:NO];
}
Albert Zou
fuente
viewWillLayoutSubviews se llama en cualquier cambio de tamaño de vista. Generalmente eso no es lo que quieres.
uliwitness
3
-(void) viewDidAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}

- (void)viewWillAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}
  • ViewWillappear lo hará instantáneamente antes de que el usuario se dé cuenta, pero ViewDidDisappear dé sección lo animará perezosamente.
  • [mytextView setContentOffset:CGPointZero animated:YES]; NO funciona a veces (no sé por qué), así que use scrollrangetovisible en su lugar.
Kahng
fuente
3

Problema resuelto: iOS = 10.2 Swift 3 UITextView

Solo usé la siguiente línea:

displayText.contentOffset.y = 0
Stephen Rowe
fuente
1
¡Funcionó (iOS 10.3)! Lo agregué adentro viewDidLayoutSubviewspara que suceda cuando la pantalla gira. De lo contrario, el desplazamiento de contenido se desalineará.
Pup
2
[self.textView scrollRectToVisible:CGRectMake(0, 0, 320, self.textView.frame.size.height) animated:NO];
Shyla
fuente
Edite con más detalles por favor.
Schemetrical
2

Tuve el problema, pero en mi caso, la vista de texto es la subvista de una vista personalizada y la agregué a ViewController. Ninguna solución funcionó para mí. Para resolver el problema, se agregó un retraso de 0.1 segundos y luego se habilitó el desplazamiento. Funcionó para mí.

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

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

Para mí funciona bien este código:

    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

Para desplazarse a la posición exacta y mostrarlo en la parte superior de la pantalla, uso este código:

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

La configuración de contentOffset.y se desplaza hasta el final del texto y luego scrollRangeToVisible se desplaza hasta el valor de scrollToLocation. Por lo tanto, la posición necesaria aparece en la primera línea de scrollView.

Igor
fuente
1

Para mí en iOS 9 dejó de funcionar para mí con una cadena atribuida con el método scrollRangeToVisible (la fila 1 estaba con fuente en negrita, otras filas estaban con fuente normal)

Entonces tuve que usar:

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

donde el retraso es:

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

Intente utilizar esta solución de 2 líneas:

view.layoutIfNeeded()
textView.contentOffset = CGPointMake(textView.contentInset.left, textView.contentInset.top)
Nikolay Shubenkov
fuente
Esto funcionó para mí. Mi UITextView estaba dentro de una UICollectionVewCell. Sospecho que scrollRangeToVisible se desplaza al lugar equivocado a menos que el diseño se calcule correctamente
John Fowler
1

Aquí están mis códigos. Funciona bien.

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()
      }
  }
David.Chu.ca
fuente