web-dev-qa-db-fra.com

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

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

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

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

Voici donc ma question: comment forcer le UITextView à défiler vers le haut chaque fois que je change le texte? J'ai essayé contentOffset et scrollRangeToVisible. Aucun travail.

Toute suggestion serait appréciée.

92
Sam Stewart
UITextView*note;
[note setContentOffset:CGPointZero animated:YES];

Ça le fait pour moi.

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

note.setContentOffset(.zero, animated: true)
144
Wayne Lo

Si quelqu'un a ce problème dans iOS 8, j'ai trouvé que le simple réglage du UITextView's propriété de texte, puis appelez scrollRangeToVisible avec un NSRange avec location:0, length:0, travaillé. Mon affichage de texte n'était pas modifiable et j'ai testé à la fois sélectionnable et non sélectionnable (aucun paramètre 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))
67
nerd2know

Et voici ma solution ...

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

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

Appel

scrollRangeToVisible(NSMakeRange(0, 0))

fonctionne mais appelez-le

override func viewDidAppear(animated: Bool)
35
RyanTCB

J'utilisais attributeString en HTML avec une vue texte non modifiable. La définition du décalage de contenu n'a pas fonctionné pour moi non plus. 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];
29
Maria

Puisqu'aucune de ces solutions n'a fonctionné pour moi et que j'ai perdu trop de temps à rassembler 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 stupide que ce ne soit pas un comportement par défaut pour ce contrôle.

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

18
Drew S.

Je ne sais pas si je comprends votre question, mais essayez-vous simplement de faire défiler la vue vers le haut? Si oui, vous devriez le faire

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

17
Benjamin Sussman

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
}

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

11
ChallengerGuy

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

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

Voilà comment je l'ai fait

Swift 4:

extension UITextView {

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

 }
8
Maksym Horbenko

Voici comment cela a fonctionné sur la version iOS 9 afin que le texteView 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;
}
8
ageorgios

Mon problème est de définir textView pour faire défiler 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
}
5
AechoLiu
[txtView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

Cette ligne de code fonctionne pour moi.

5
Patricia

Combiné les réponses précédentes, maintenant cela devrait 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
5
Géza Mikló

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 se soient déclenchés ou que ce que vous faites soit terminé.

5
John Ballinger

Swift 2 Réponse:

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.

4
glace

Dans Swift 3:

  • viewWillLayoutSubviews est l'endroit pour effectuer des modifications. viewWillAppear devrait également fonctionner, mais logiquement, layout devrait être exécuté dans viewWillLayoutSubviews.

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

Supposons que ITextView est nommé 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)
}
4
Marco Leong

Cela a fonctionné pour moi

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

    DispatchQueue.main.async {
      self.textView.scrollRangeToVisible(NSRange(location: 0, length: 0))
    }
  }
3
Ankit garg
-(void) viewDidAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}

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

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)];
}
3
Hermann Klecker

vous pouvez juste utiliser ce code

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

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

Je viens d'utiliser la ligne suivante:

displayText.contentOffset.y = 0
3
Stephen Rowe

J'ai eu le problème, mais dans mon cas, textview est la sous-vue d'une vue personnalisée et l'a ajoutée à ViewController. Aucune solution n'a fonctionné pour moi. Pour résoudre le problème, ajoutez un délai de 0,1 seconde, puis activez le défilement. Ça a marché 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
}
2
SaRaVaNaN DM
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [textview setContentOffset:CGPointZero animated:NO];
}
2
Albert Zou
[self.textView scrollRectToVisible:CGRectMake(0, 0, 320, self.textView.frame.size.height) animated:NO];
2
shyla

Pour moi, ça 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 défile jusqu'à la fin du texte, puis scrollRangeToVisible défile jusqu'à la valeur de scrollToLocation. Ainsi, la position requise apparaît dans la première ligne de scrollView.

1
Igor

Essayez d'utiliser cette solution à 2 lignes:

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

Je pense que la raison pour laquelle certaines personnes rencontrent des problèmes avec ce problème est qu'elles essaient de définir la position de défilement TextView, avant que ce TextView n'apparaisse.

Il n'y a rien à faire défiler, quand le TextView n'est pas encore apparu.

Vous devez attendre que TextView apparaisse en premier, puis seulement définir sa position de défilement dans la méthode viewDidAppear comme indiqué ci-dessous:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    textView.setContentOffset(CGPoint.zero, animated: false)
}
1
Mike

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

Ce code résout le problème et permet également d'éviter le défilement automatique inutile jusqu'à la fin de textView. Utilisez simplement la méthode setTextPreservingContentOffset: au lieu de définir directement le texte sur textView.

@interface ViewController () {
    __weak IBOutlet UITextView *textView;
    CGPoint lastContentOffset;
    NSTimer *autoscrollTimer;
    BOOL revertAnyContentOffsetChanges;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [textView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)dealloc
{
    [textView removeObserver:self forKeyPath:@"contentOffset"];
}

- (void)setTextPreservingContentOffset:(NSString *)text
{
    lastContentOffset = textView.contentOffset;
    [autoscrollTimer invalidate];
    revertAnyContentOffsetChanges = YES;
    textView.text = text;
    autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(enableScrolling:) userInfo:nil repeats:NO];
    autoscrollTimer.tolerance = 1;
}

- (void)enableScrolling:(NSTimer *)timer
{
    revertAnyContentOffsetChanges = NO;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (revertAnyContentOffsetChanges && [keyPath isEqualToString:@"contentOffset"] && [[change valueForKey:@"new"] CGPointValue].y != lastContentOffset.y) {
        [textView setContentOffset:lastContentOffset animated:NO];
    }
}

@end
1
Terry

J'ai dû appeler ce qui suit à l'intérieur viewDidLayoutSubviews (appeler à l'intérieur de viewDidLoad était trop tôt):

    myTextView.setContentOffset(.zero, animated: true)
1

Pour moi dans iOS 9, cela a cessé de fonctionner pour moi avec la chaîne attribuée avec la méthode scrollRangeToVisible (la 1 ligne était en police en gras, les autres lignes étaient en 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)
}
1
Paul T.

La version Swift 3 de la réponse de drew_s plus tôt, qui est la seule chose qui a fonctionné pour moi après avoir essayé la plupart des autres réponses.

    override func viewWillAppear(_ animated: Bool) {
    DispatchQueue.main.async{
        let desiredOffset = CGPoint(x: 0, y: -self.person_description.contentInset.top)
        self.person_description.setContentOffset(desiredOffset, animated: false)
    }
}

Il s'agissait d'un copier-coller de code de travail. Remplacez person_description par votre variable textview.

1
hawmack13

La réponse de ChallengerGuy a complètement résolu mon problème une fois que j'ai ajouté un bref délai avant de réactiver le défilement. Avant d'ajouter le délai, mes pages PageController étaient toutes fixes, à l'exception de la première page, qui se corrigeait également après toute interaction de l'utilisateur avec la page. L'ajout du délai a également corrigé la position de défilement UITextView de cette première page.

override open func viewDidLoad() {
    super.viewDidLoad()
    textView.isScrollEnabled = false
    textView.text = textToScroll            
   ... other stuff
}

override open func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    Central().delay(0.5) {
        self.textView.isScrollEnabled = true
    }
}

(La fonction de retard dans mon fichier Central ().)

func delay(_ delay: Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
        closure()
    }
}
0
Wayne Henderson

Pour Swift je pense que la façon la plus propre de le faire est de sous-classer UITextView et d'utiliser cette petite astuce

import UIKit

class MyTextView: UITextView {
    override var text: String! {
        willSet {
            isScrollEnabled = false
        } didSet {
            isScrollEnabled = true
        }
    }
}
0
Adam