Comment forcer un UITextView à défiler vers le haut chaque fois que je change le texte?

100

OK, j'ai un problème avec le UITextView. Voici le problème:

J'ajoute du texte à un fichier UITextView. L'utilisateur double ensuite pour sélectionner quelque chose. Je change ensuite le texte dans le UITextView(par programme comme ci-dessus) et les UITextView défilements vers le bas de la page où il y a un curseur.

Cependant, ce n'est PAS là que l'utilisateur a cliqué. Il défile TOUJOURS vers le bas de, UITextViewquel que soit l'endroit où l'utilisateur a cliqué.

Voici donc ma question: comment forcer le UITextViewdéfilement vers le haut chaque fois que je change le texte? J'ai essayé contentOffsetet scrollRangeToVisible. Aucun travail.

Toute suggestion serait appréciée.

Sam Stewart
la source

Réponses:

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

Cela le fait pour moi.

Version Swift (Swift 4.1 avec iOS 11 sur Xcode 9.3):

note.setContentOffset(.zero, animated: true)
Wayne Lo
la source
13
Ne m'aidez pas.
Dmitry
6
Cela fonctionne, mais pourquoi est-ce nécessaire de toute façon? La logique de défilement d'Apple doit être affinée, il y a trop de sauts de cerceau pour que les choses se comportent comme elles le devraient automatiquement dans la plupart des cas.
John Contarino
4
Fonctionne pour moi dans iOS 9 mais pas avant viewDidAppear().
Murray Sagal
4
Vous pouvez appeler cela plus tôt que viewDidAppear () en l'appelant dans viewDidLayoutSubviews ()
arcady bob
J'ai ajouté une vue de texte dans la cellule du tableau, donc je mets cela dans la cellule pour la ligne mais ne fonctionne pas
Chandni
67

Si quelqu'un a ce problème dans iOS 8, je trouve que la mise en juste la UITextView'spropriété de texte, puis en appelant scrollRangeToVisibleavec un NSRangeavec location:0, length:0, travaillé. Ma vue de texte n'était pas modifiable, et j'ai testé à la fois sélectionnable et non sélectionnable (aucun des paramètres n'a affecté le résultat). Voici un exemple Swift:

myTextView.text = "Text that is long enough to scroll"
myTextView.scrollRangeToVisible(NSRange(location:0, length:0))
nerd2know
la source
merci, rien de ce qui précède n'a fonctionné pour moi. Votre échantillon l'a fait.
user1244109
Cela fonctionne, mais nous obtenons une animation de défilement qui doit être désactivée, je préfère désactiver le défilement et le réactiver comme @miguel l'a suggéré
Warpzit
Objective-C: [myTextView scrollRangeToVisible: NSMakeRange (0, 0)];
Stateful
Cela ne semble plus fonctionner. iOS 9 et Xcode 7.3.1: /
wasimsandhu
Swift 3 Stll fonctionne! :) copiez simplement cette ligne dans votre méthode UITextFielDelegate. `func textViewDidEndEditing (_ textView: UITextView) {textView.scrollRangeToVisible (NSRange (location: 0, longueur: 0))}`
lifewithelliott
46

Et voici ma solution ...

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

    override func viewDidAppear(animated: Bool) {
        textView.scrollEnabled = true
    }
miguel
la source
5
Je n'aime pas cette solution, mais au moins à partir d'iOS 9 beta 5, c'est la seule chose qui fonctionne pour moi. Je suppose que la plupart des autres solutions supposent que votre vue de texte est déjà visible. Dans mon cas, ce n'est pas toujours vrai, je dois donc continuer à désactiver le défilement jusqu'à ce qu'il apparaisse.
robotspacer
1
C'était la seule chose qui fonctionnait pour moi aussi. iOS 9, en utilisant une vue de texte non modifiable.
SeanR
1
Cela fonctionne pour moi, texte non modifiable iOS 8 avec chaîne attribuée. En vueWillAppear - ne fonctionne pas
Tina Zh
Je suis d'accord avec @robotspacer. et cette solution est toujours la seule qui fonctionne pour moi.
lerp90
Pour moi, cette solution ne fonctionnait qu'avec des textes courts (attribués). Lorsque vous avez des textes plus longs, le bogue de défilement UITextView reste en vie :-) Ma solution (un peu moche) était d'utiliser dispatch_after avec un délai de 2 secondes pour réactiver le défilement. UITextView semble avoir besoin d'un peu de temps pour faire sa mise en page de texte ...
LaborEtArs
38

Appel

scrollRangeToVisible(NSMakeRange(0, 0))

fonctionne mais appelle-le

override func viewDidAppear(animated: Bool)
RyanTCB
la source
4
Je confirme que cela n'a fonctionné que pour moi dans viewDidAppear.
Eric f.
2
C'est le seul qui fonctionne pour moi. THX. Mais comment désactiver l'animation que je trouve légèrement ennuyeuse?
rockhammer le
Bien que cela fonctionne, la solution de I @Maria est meilleure, car celle-ci ne montre pas un bref "saut".
Sipke Schoorstra
1
Je reprends mon dernier commentaire; c'est la meilleure réponse pour moi car cela fonctionne à la fois sur iOS 10 et iOS 11, alors que la réponse de @ Maria ne fonctionne pas bien sur iOS 11.
Sipke Schoorstra
@SipkeSchoorstra, voir ma réponse ci-dessous pour le faire fonctionner sans aucun saut (en utilisant KVO).
Terry
29

J'utilisais attribuéString en HTML avec une vue texte non modifiable. La définition du décalage de contenu n'a pas non plus fonctionné pour moi. Cela a fonctionné pour moi: désactiver le défilement activé, définir le texte, puis réactiver le défilement

[yourTextView setScrollEnabled:NO];
yourTextView.text = yourtext;
[yourTextView setScrollEnabled:YES];
Maria
la source
J'ai eu un textView à l'intérieur d'une tableViewCell. Dans ce cas, le textView a défilé jusqu'à env. 50%. Cette solution est aussi simple que logique. Merci
Julian F. Weinert
1
@ JulianF.Weinert Cela ne semble pas fonctionner pour iOS10. J'ai la même situation que vous avec le textView dans une tableViewCell. Je n'utilise pas la capacité d'édition du textView. Je veux juste du texte déroulant. Mais le textView défile toujours à environ 50% comme vous l'avez dit. J'ai également essayé toutes les autres suggestions en définissant setContentOffset et scrollRangeToVisible dans cet article. Aucun travail. Avez-vous pu faire autre chose pour que cela fonctionne?
JeffB6688
@ JeffB6688 désolé, non. C'est vraiment il y a longtemps ... Peut-être une autre bizarrerie d'une "nouvelle" version iOS?
Julian F. Weinert
Mais pas parfaitement sur iOS 11.2 malheureusement. La réponse d'Onlu @RyanTCB fonctionne à la fois sur iOS 10 et iOS 11.
Sipke Schoorstra
Cela fonctionne très bien pour moi jusqu'à présent sur iOS 9, 10 et 11, mais j'ai dû l'activer dans viewDidAppearle cadre d'un plus grand nombre de correctifs
MadProgrammer
19

Comme aucune de ces solutions n'a fonctionné pour moi et que j'ai perdu beaucoup trop de temps à assembler des solutions, cela a finalement résolu le problème pour moi.

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

C'est vraiment idiot que ce ne soit pas le comportement par défaut pour ce contrôle.

J'espère que cela aide quelqu'un d'autre.

Drew S.
la source
2
Cela a été très utile! J'ai dû également ajouter: self.automaticallyAdjustsScrollViewInsets = NO; à la vue contenant le UITextView pour le faire fonctionner pleinement.
jengelsma
Je ne l'ai fait fonctionner que dans SWIFT 3, avec un autre calcul de décalage self.textView.contentOffset.ybasé sur cette réponse: stackoverflow.com/a/25366126/1736679
Efren
A travaillé pour moi une fois que je me suis adapté à la dernière syntaxe pour Swift 3
hawmack13
totalement d'accord à quel point est-ce fou? merci pour la solution
ajonno
17

Je ne sais pas si j'ai bien compris votre question, mais essayez-vous simplement de faire défiler la vue vers le haut? Si c'est le cas, vous devriez faire

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

Benjamin Sussman
la source
1
@SamStewart Le lien semble rompu, mène toujours à ma page de comptes (déjà connecté) pouvez-vous fournir plus d'informations sur la solution qu'ils proposent là-bas?
uliwitness
@uliwitness Ce sont des informations très anciennes maintenant, je recommanderais d'essayer une ressource plus moderne au lieu d'un post vieux de 8 ans. L'API iOS a radicalement changé au cours de ces 8 années.
Benjamin Sussman
1
J'ai essayé de trouver des informations plus récentes. Si vous savez où je peux le trouver, je vous en serais reconnaissant. Semble toujours être le même problème :(
uliwitness
13

Pour iOS 10 et Swift 3, j'ai fait:

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

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

Cela a fonctionné pour moi, je ne sais pas si vous rencontrez le problème avec la dernière version de Swift et iOS.

ChallengerGuy
la source
Une solution parfaite à suivre!
iChirag
J'ai le problème de défilement avec un UITextView dans un UIView contrôlé par un UIPageViewController. (Ces pages affichent mes écrans Aide / À propos.) Votre solution fonctionne pour toutes les pages sauf la première. Le fait de glisser vers n'importe quelle page et de revenir à la première page fixe la position de défilement de cette première page. J'ai essayé d'ajouter un autre .isScrollEnabled = False dans le viewWillAppear mais cela n'a eu aucun effet. Je ne sais pas pourquoi la première page se comporte différemment du reste.
Wayne Henderson
[mise à jour] Je viens de le résoudre! Introduire un délai de 0,5 seconde avant que les déclencheurs .isScrollEnabled = true résout le problème de premier chargement.
Wayne Henderson
très agréable . . .
Maysam R
A fonctionné comme un charme. La seule solution qui fonctionne pour moi. Tu es l'homme.
Lazy Ninja
10

J'ai une réponse mise à jour qui fera apparaître la vue de texte correctement, et sans que l'utilisateur ne subisse une animation de défilement.

override func viewWillAppear(animated: Bool) {
    dispatch_async(dispatch_get_main_queue(), {
        self.introductionText.scrollRangeToVisible(NSMakeRange(0, 0))
    })
MacInnis
la source
Pourquoi faites-vous dans la file d'attente principale, sine viewWillAppear est en cours d'exécution sur la file d'attente principale?
karthikPrabhu Alagu
1
Il y a trop de lunes pour que je m'en souvienne, mais c'est traité ici: stackoverflow.com/a/17490329/4750649
MacInnis
8

Voici comment cela fonctionnait sur la version iOS 9 afin que le textView défile en haut avant d'apparaître à l'écran

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

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    textView.scrollEnabled = YES;
}
ageorgios
la source
1
Cela a fait l'affaire. Incidemment, cela m'a appris quelque chose de très important: la 'vue' dans 'viewwillappear' ne se réfère pas seulement à self.view, mais apparemment à TOUTES les vues. Non?
Sjakelien
8

Voilà comment je l'ai fait

Swift 4:

extension UITextView {

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

 }
Maksym Horbenko
la source
C'est la seule méthode qui donne les meilleurs résultats pour moi. Je vote.
smoothBlue
7

Cela a fonctionné pour moi

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

    DispatchQueue.main.async {
      self.textView.scrollRangeToVisible(NSRange(location: 0, length: 0))
    }
  }
Garg Ankit
la source
C'est vraiment bizarre. Sur la plupart des appareils, il DispatchQueue.main.async(...n'est pas nécessaire d' encapsuler l'appel , mais pour une raison quelconque, sur (par exemple) l'iPhone 5s (iOS 12.1), cela ne fonctionne que si vous expédiez. Mais comme indiqué par la pile d'appels sur le débogueur, viewWillAppear() est déjà en cours d'exécution sur la file d'attente principale dans tous les cas ... - WTF, Apple ???
Nicolas Miari
2
Je me demandais également pourquoi je devais envoyer explicitement sur la file d'attente principale même si j'exécute tous les événements sur la file d'attente principale. Mais cela a fonctionné pour moi
Ankit garg
6

Essayez ceci pour déplacer le curseur en haut du texte.

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

Voyez comment ça se passe. Assurez-vous d'appeler cela après que tous vos événements ont été déclenchés ou que ce que vous faites soit terminé.

John Ballinger
la source
essayé l'homme, pas de chance. Cela semble être quelque chose avec le premier répondeur. D'autres suggestions?
Sam Stewart
J'ai le même problème avec la position du curseur, cela a fonctionné pour moi ... super +1 pour ça.
Dhaval
J'ai une animation de bas en haut mais je n'ai pas besoin de cela, alors comment l'enlever.
Dhaval
2
Il ne défile pas vers le haut (moins sur quelques pixels).
Dmitry
6

Mon problème est de configurer textView pour qu'il défile vers le haut lorsque la vue est apparue. Pour iOS 10.3.2 et Swift 3.

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

Solution 2:

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

Combiné les réponses précédentes, cela devrait maintenant fonctionner:

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ó
la source
lmao la seule chose qui a fonctionné pour moi comment stupide. J'ai pu supprimer scrollRangeToVisiblecependant.
Jordan H
Vous devrez remplacer NSRange (0,0) par NSMakeRange (0,0)
RobP
5
[txtView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

Cette ligne de code fonctionne pour moi.

Patricia
la source
5

tu peux juste utiliser ce code

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

Réponse Swift 2:

textView.scrollEnabled = false

/* Set the content of your textView here */

textView.scrollEnabled = true

Cela empêche le textView de défiler jusqu'à la fin du texte après l'avoir défini.

glacé
la source
4

Dans Swift 3:

  • viewWillLayoutSubviews est l'endroit où effectuer des modifications. viewWillAppear doit également fonctionner, mais logiquement, la mise en page doit être effectuée dans viewWillLayoutSubviews .

  • En ce qui concerne les méthodes, scrollRangeToVisible et setContentOffset fonctionneront. Cependant, setContentOffset permet à l'animation d'être désactivée ou activée .

Supposons que UITextView est nommé comme yourUITextView . Voici le code:

// 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
la source
viewWillLayoutSubviews est appelé à tout redimensionnement de vue. Ce n'est généralement pas ce que vous voulez.
uliwitness
4

J'ai dû appeler le viewDidLayoutSubviews intérieur suivant (l'appel à l'intérieur de viewDidLoad était trop tôt):

    myTextView.setContentOffset(.zero, animated: true)
Charlie Seligman
la source
ne fonctionne pas dans tous les scénarios
Raja Saad
3

Cela a fonctionné pour moi. Il est basé sur la réponse de RyanTCB mais c'est la variante Objective-C de la même solution:

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

    [textview setContentOffset:CGPointZero animated:NO];
}
Albert Zou
la source
viewWillLayoutSubviews est appelé à tout redimensionnement de vue. Ce n'est généralement pas ce que vous voulez.
uliwitness
3
-(void) viewDidAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}

- (void)viewWillAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}
  • ViewWillappearle fera instantanément avant que l'utilisateur ne puisse le remarquer, mais la ViewDidDisappearsection l'animera paresseusement.
  • [mytextView setContentOffset:CGPointZero animated:YES]; ne fonctionne PAS parfois (je ne sais pas pourquoi), alors utilisez plutôt scrollrangetovisible.
Kahng
la source
3

Problème résolu: iOS = 10.2 Swift 3 UITextView

Je viens d'utiliser la ligne suivante:

displayText.contentOffset.y = 0
Stephen Rowe
la source
1
A travaillé (iOS 10.3)! Je l'ai ajouté à l'intérieur viewDidLayoutSubviewsdonc cela se produit lorsque l'écran tourne. Sinon, le décalage du contenu n'est pas aligné.
Pup
2
[self.textView scrollRectToVisible:CGRectMake(0, 0, 320, self.textView.frame.size.height) animated:NO];
Shyla
la source
Modifiez avec plus de détails s'il vous plaît.
Schéma
2

J'ai eu le problème, mais dans mon cas, textview est la sous-vue d'une vue personnalisée et l'ajoute à ViewController. Aucune des solutions n'a fonctionné pour moi. Pour résoudre le problème, un délai de 0,1 seconde a été ajouté, puis le défilement activé. Cela a fonctionné pour moi.

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
la source
1

Pour moi fonctionne bien ce code:

    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

Pour faire défiler jusqu'à la position exacte et l'afficher en haut de l'écran, j'utilise ce code:

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

La définition de contentOffset.y fait défiler jusqu'à la fin du texte, puis scrollRangeToVisible fait défiler jusqu'à la valeur de scrollToLocation. Ainsi, la position requise apparaît dans la première ligne de scrollView.

Igor
la source
1

Pour moi, dans iOS 9, cela a cessé de fonctionner pour moi avec une chaîne attribuée avec la méthode scrollRangeToVisible (la 1 ligne était avec une police en gras, les autres lignes étaient avec une police normale)

J'ai donc dû utiliser:

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

où le retard est:

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.
la source
1

Essayez d'utiliser cette solution à 2 lignes:

view.layoutIfNeeded()
textView.contentOffset = CGPointMake(textView.contentInset.left, textView.contentInset.top)
Nikolay Shubenkov
la source
Cela a fonctionné pour moi. Mon UITextView était à l'intérieur d'un UICollectionVewCell. Je soupçonne que scrollRangeToVisible défile jusqu'au mauvais endroit à moins que la mise en page ne soit calculée correctement
John Fowler
1

Voici mes codes. Ça fonctionne 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
la source