Como faço para forçar um UITextView a rolar para o topo toda vez que altero o texto?

100

OK, estou tendo problemas com o UITextView. Aqui está o problema:

Eu adiciono algum texto a a UITextView. O usuário então clica duas vezes para selecionar algo. Em seguida, altero o texto no UITextView(programaticamente como acima) e os UITextView pergaminhos para a parte inferior da página, onde há um cursor.

No entanto, NÃO foi aí que o usuário clicou. SEMPRE rola para a parte inferior do, UITextViewindependentemente de onde o usuário clicou.

Portanto, aqui está minha pergunta: Como faço para forçar a UITextViewrolagem para o topo toda vez que altero o texto? Eu tentei contentOffsete scrollRangeToVisible. Nenhum trabalho.

Qualquer sugestão seria apreciada.

Sam Stewart
fonte

Respostas:

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

Isso faz por mim.

Versão Swift (Swift 4.1 com iOS 11 no Xcode 9.3):

note.setContentOffset(.zero, animated: true)
Wayne Lo
fonte
13
Não me ajude.
Dmitry
6
Isso funciona, mas por que isso é necessário? A lógica de rolagem da Apple precisa de refinamento, há muitos obstáculos para fazer as coisas se comportarem da maneira que deveriam automaticamente na maioria dos casos.
John Contarino
4
Funciona para mim no iOS 9, mas não antes viewDidAppear().
Murray Sagal
4
Você pode chamar isso antes de viewDidAppear () chamando-o em viewDidLayoutSubviews ()
arcady bob
Eu adicionei a visualização de texto na célula da tabela, então estou definindo isso na célula para a linha, mas não estou funcionando
Chandni
67

Se alguém tem esse problema no iOS 8, descobri que apenas definir o UITextView'spropriedade de texto, em seguida, chamando scrollRangeToVisiblecom um NSRangecom location:0, length:0, funcionou. Minha visualização de texto não era editável e testei selecionável e não selecionável (nenhuma configuração afetou o resultado). Aqui está um exemplo do Swift:

myTextView.text = "Text that is long enough to scroll"
myTextView.scrollRangeToVisible(NSRange(location:0, length:0))
nerd2know
fonte
obrigado, nenhuma das opções acima funcionou para mim. Sua amostra sim.
user1244109
Isso funciona, mas temos uma animação de rolagem que precisa ser desabilitada, eu prefiro desabilitar a rolagem e reativar como sugeriu
@miguel
Objective-C: [myTextView scrollRangeToVisible: NSMakeRange (0, 0)];
Stateful
Isso parece não funcionar mais. iOS 9 e Xcode 7.3.1: /
wasimsandhu
Swift 3 Stll funciona! :) basta copiar esta linha em seu método UITextFielDelegate. `func textViewDidEndEditing (_ textView: UITextView) {textView.scrollRangeToVisible (NSRange (location: 0, length: 0))}`
lifewithelliott
46

E aqui está minha solução ...

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

    override func viewDidAppear(animated: Bool) {
        textView.scrollEnabled = true
    }
miguel
fonte
5
Eu não amo essa solução, mas pelo menos a partir do iOS 9 beta 5, é a única coisa que funciona para mim. Estou assumindo que a maioria das outras soluções estão assumindo que sua visualização de texto já está visível. No meu caso, isso nem sempre é verdade, então tenho que manter a rolagem desabilitada até que apareça.
robotspacer de
1
Essa foi a única coisa que funcionou para mim também. iOS 9, usando uma visualização de texto não editável.
SeanR
1
Isso funciona para mim, o textview não editável do iOS 8 com string atribuída. Em viewWillAppear - não funciona
Tina Zh
Eu concordo com @robotspacer. e esta solução ainda é a única que funciona para mim.
lerp90
Para mim, essa solução funcionou apenas com textos curtos (atribuídos). Ao ter textos mais longos, o bug de rolagem UITextView continua vivo :-) Minha (um pouco feia) solução foi usar dispatch_after com um atraso de 2 segundos para reativar a rolagem. UITextView parece precisar de um pouco de tempo para fazer seu layout de texto ...
LaborEtArs
38

Chamando

scrollRangeToVisible(NSMakeRange(0, 0))

funciona, mas ligue

override func viewDidAppear(animated: Bool)
RyanTCB
fonte
4
Confesso que só funcionou para mim em viewDidAppear.
eric f.
2
Este é o único que funciona para mim. THX. Mas como você desativa a animação que eu acho um pouco chata?
rockhammer
Embora funcione, a solução da I @Maria é melhor, porque essa não mostra um breve "salto".
Sipke Schoorstra
1
Retiro meu último comentário; esta é a melhor resposta para mim, pois funciona no iOS 10 e no iOS 11, enquanto a resposta de
@Maria
@SipkeSchoorstra, veja minha resposta abaixo para fazer funcionar sem saltos (usando KVO).
Terry
29

Eu estava usando attributeString em HTML com visualização de texto não editável. Definir o deslocamento de conteúdo também não funcionou para mim. Isso funcionou para mim: desabilite a rolagem habilitada, defina o texto e habilite a rolagem novamente

[yourTextView setScrollEnabled:NO];
yourTextView.text = yourtext;
[yourTextView setScrollEnabled:YES];
Maria
fonte
Eu tinha um textView dentro de um tableViewCell. Neste caso, textView foi rolado para aprox. 50%. Esta solução é tão simples quanto lógica. Obrigado
Julian F. Weinert
1
@ JulianF.Weinert Isso não parece funcionar para iOS10. Eu tenho a mesma situação que você com o textView em um tableViewCell. Não estou usando o recurso de edição do textView. Eu só quero texto rolável. Mas o textView é sempre rolado para aproximadamente 50%, como você disse. Também tentei todas as outras sugestões com a configuração de setContentOffset e scrollRangeToVisible neste post. Nenhum funciona. Existe mais alguma coisa que você possa ter feito para fazer este trabalho?
JeffB6688
@ JeffB6688 desculpe, não. Isso foi realmente há muito tempo ... Pode ser outra peculiaridade de um "novo" lançamento do iOS?
Julian F. Weinert
Mas não perfeitamente no iOS 11.2, infelizmente. A resposta de Onlu @RyanTCB funciona tanto no iOS 10 quanto no iOS 11.
Sipke Schoorstra
Isso funciona bem para mim até agora no iOS 9, 10 e 11, mas tive que habilitá-lo viewDidAppearcomo parte de uma série mais ampla de correções
MadProgrammer
19

Como nenhuma dessas soluções funcionou para mim e eu perdi muito tempo reunindo soluções, isso finalmente resolveu para mim.

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

É realmente bobo que este não seja o comportamento padrão para este controle.

Espero que isso ajude alguém.

Drew S.
fonte
2
Isso foi muito útil! Tive de adicionar também: self.automaticallyAdjustsScrollViewInsets = NO; à visão que contém o UITextView para que funcione totalmente.
jengelsma
Eu só fiz funcionar no SWIFT 3, com outro cálculo de deslocamento com self.textView.contentOffset.ybase nesta resposta: stackoverflow.com/a/25366126/1736679
Efren
Funcionou para mim uma vez que me adaptei à sintaxe mais recente para Swift 3
hawmack13
concordo totalmente o quão louco é isso? obrigado pela solução
ajonno
17

Não tenho certeza se entendi sua pergunta, mas você está tentando simplesmente rolar a visualização para o topo? Se sim, você deve fazer

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

Benjamin Sussman
fonte
1
@SamStewart O link parece estar quebrado, sempre leva à página de minhas contas (já conectado) você pode fornecer mais informações sobre que solução eles oferecem lá?
uliwitness
@uliwitness Esta é uma informação super antiga agora, eu recomendaria tentar um recurso mais moderno em vez de um post de 8 anos. A API do iOS mudou drasticamente nesses 8 anos.
Benjamin Sussman
1
Tentei encontrar informações mais recentes. Se você souber onde posso encontrar, eu agradeceria. Ainda parece ser o mesmo problema :(
uliwitness
13

Para iOS 10 e Swift 3, fiz:

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

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

Funcionou para mim, não tenho certeza se você está tendo problemas com a versão mais recente do Swift e iOS.

ChallengerGuy
fonte
Uma solução perfeita a seguir!
iChirag
Estou tendo o problema de rolagem com um UITextView em um UIView controlado por um UIPageViewController. (Estas páginas mostram minhas telas de Ajuda / Sobre.) Sua solução funciona para todas as páginas, exceto a primeira. Deslizar para qualquer página e voltar para a primeira página corrige a posição de rolagem da primeira página. Tentei adicionar outro .isScrollEnabled = False ao viewWillAppear, mas não teve efeito. Estou perplexo quanto ao motivo pelo qual a primeira página se comporta de maneira diferente do resto.
Wayne Henderson
[atualização] Acabei de resolver! Introduzir um atraso de 0,5 segundos antes que os gatilhos .isScrollEnabled = true corrijam o problema de primeiro carregamento.
Wayne Henderson
muito agradável . . .
Maysam R
Funcionou como um encanto. A única solução que funciona para mim. Você é o cara.
Lazy Ninja
10

Tenho uma resposta atualizada que fará com que a visualização do texto apareça corretamente e sem que o usuário experimente uma animação de rolagem.

override func viewWillAppear(animated: Bool) {
    dispatch_async(dispatch_get_main_queue(), {
        self.introductionText.scrollRangeToVisible(NSMakeRange(0, 0))
    })
MacInnis
fonte
Por que você está fazendo na fila principal, o sine viewWillAppear está em execução na fila principal?
karthikPrabhu Alagu
1
Muitas luas atrás para que eu me lembre, mas está endereçado aqui: stackoverflow.com/a/17490329/4750649
MacInnis
8

É assim que funcionava no iOS 9 Release, de modo que textView é rolado na parte superior antes de aparecer na tela

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

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    textView.scrollEnabled = YES;
}
Ageorgios
fonte
1
Isso funcionou. A propósito, isso me ensinou algo muito importante: o 'view' em 'viewwillappear' não se refere apenas a self.view, mas aparentemente a TODOS os views. Não?
Sjakelien
8

Foi assim que eu fiz

Swift 4:

extension UITextView {

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

 }
Maksym Horbenko
fonte
Este é o único método que produz os melhores resultados para mim. Eu voto positivo.
smoothBlue
7

Isso funcionou para mim

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

    DispatchQueue.main.async {
      self.textView.scrollRangeToVisible(NSRange(location: 0, length: 0))
    }
  }
Ankit garg
fonte
Isso é muito estranho. Na maioria dos dispositivos, encerrar a chamada DispatchQueue.main.async(...não é necessário, mas por algum motivo, no (por exemplo) iPhone 5s (iOS 12.1), ele não funciona a menos que você envie. Mas conforme relatado pela pilha de chamadas no depurador, viewWillAppear() já está rodando na fila principal em todos os casos ... - WTF, Apple ???
Nicolas Miari
2
Também estava me perguntando por que tenho que despachar explicitamente na fila principal, embora esteja executando todos os eventos na fila principal. Mas isso funcionou para mim
Ankit garg
6

Tente fazer isso para mover o cursor para o topo do texto.

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

Veja como isso vai. Certifique-se de ligar assim após todos os seus eventos terem disparado ou o que quer que você esteja fazendo estiver feito.

John Ballinger
fonte
tentei cara, sem sorte. Parece ser algo com o firstResponder. Alguma outra sugestão?
Sam Stewart
tenho o mesmo problema com a posição do cursor, funcionou para mim ... ótimo +1 para isso.
Dhaval de
Eu tenho animação de baixo para cima, mas eu não exigi isso, então como removê-la.
Dhaval
2
Não rola para cima (menos em alguns pixels).
Dmitry de
6

Meu problema é definir textView para rolar para o topo quando view apareceu. Para iOS 10.3.2 e Swift 3.

Solução 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)
}

Solução 2:

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

Combinadas as respostas anteriores, agora deve 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ó
fonte
lmao a única coisa que funcionou para mim que bobo. Eu fui capaz de remover, no scrollRangeToVisibleentanto.
Jordan H
Você precisará substituir NSRange (0,0) por NSMakeRange (0,0)
RobP
5
[txtView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

Esta linha de código funciona para mim.

Patricia
fonte
5

só você pode usar este código

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

Resposta do Swift 2:

textView.scrollEnabled = false

/* Set the content of your textView here */

textView.scrollEnabled = true

Isso evita que o textView role para o final do texto após configurá-lo.

glace
fonte
4

Em Swift 3:

  • viewWillLayoutSubviews é o lugar para fazer alterações. viewWillAppear também deve funcionar, mas logicamente, o layout deve ser executado em viewWillLayoutSubviews .

  • Com relação aos métodos, scrollRangeToVisible e setContentOffset funcionarão. No entanto, setContentOffset permite que a animação seja desativada ou ativada .

Suponha que UITextView seja nomeado como yourUITextView . Aqui está o 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
fonte
viewWillLayoutSubviews é chamado em qualquer redimensionamento de visualização. Geralmente não é isso que você quer.
uliwitness
4

Tive que chamar o seguinte dentro de viewDidLayoutSubviews (chamar dentro de viewDidLoad era muito cedo):

    myTextView.setContentOffset(.zero, animated: true)
Charlie Seligman
fonte
não funciona em todos os cenários
Raja Saad
3

Isso funcionou para mim. É baseado na resposta da RyanTCB, mas é a variante Objective-C da mesma solução:

- (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
fonte
3
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [textview setContentOffset:CGPointZero animated:NO];
}
Albert Zou
fonte
viewWillLayoutSubviews é chamado em qualquer redimensionamento de visualização. Geralmente não é isso que você quer.
uliwitness
3
-(void) viewDidAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}

- (void)viewWillAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}
  • ViewWillappear fará isso instantaneamente antes que o usuário perceba, mas ViewDidDisappear seção irá animá-la preguiçosamente.
  • [mytextView setContentOffset:CGPointZero animated:YES]; NÃO funciona às vezes (não sei por quê), então use scrollrangetovisible.
Kahng
fonte
3

Problema resolvido: iOS = 10.2 Swift 3 UITextView

Acabei de usar a seguinte linha:

displayText.contentOffset.y = 0
Stephen Rowe
fonte
1
Funcionou (iOS 10.3)! Eu o adicionei viewDidLayoutSubviewspara que aconteça quando a tela girar. Caso contrário, o deslocamento de conteúdo ficará desalinhado.
Filhote de
2
[self.textView scrollRectToVisible:CGRectMake(0, 0, 320, self.textView.frame.size.height) animated:NO];
Shyla
fonte
Edite com mais detalhes, por favor.
Schemetrical
2

Eu tive o problema, mas no meu caso textview é a subview de alguma view customizada e a adicionei ao ViewController. Nenhuma das soluções funcionou para mim. Para resolver o problema, adicionou um atraso de 0,1 seg e depois habilitou a rolagem. Funcionou para mim

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
fonte
1

Para mim, funciona bem 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 rolar para a posição exata e mostrá-lo no topo da tela, eu uso este código:

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

A configuração de contentOffset.y rola até o final do texto e, em seguida, scrollRangeToVisible rola até o valor de scrollToLocation. Assim, a posição necessária aparece na primeira linha de scrollView.

Igor
fonte
1

Para mim, no iOS 9, parou de funcionar para mim com string atribuída com o método scrollRangeToVisible (a 1 linha estava com fonte em negrito, outras linhas estavam com fonte regular)

Então eu tive 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)
            })

onde o atraso é:

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.
fonte
1

Tente usar esta solução de 2 linhas:

view.layoutIfNeeded()
textView.contentOffset = CGPointMake(textView.contentInset.left, textView.contentInset.top)
Nikolay Shubenkov
fonte
Isso funcionou para mim. Meu UITextView estava dentro de um UICollectionVewCell. Suspeito que scrollRangeToVisible rola para o local errado, a menos que o layout seja calculado corretamente
John Fowler,
1

Aqui estão meus códigos. Funciona bem.

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
fonte