Jak zmusić UITextView do przewijania do góry za każdym razem, gdy zmieniam tekst?


100

OK, mam problem z plikiem UITextView. Oto problem:

Dodaję tekst do pliku UITextView. Następnie użytkownik klika dwukrotnie, aby coś wybrać. Następnie zmieniam tekst w UITextView(programowo jak powyżej) i UITextView przewija się na dół strony, gdzie znajduje się kursor.

Jednak NIE tam kliknął użytkownik. ZAWSZE przewija się na sam dół UITextViewniezależnie od tego, gdzie użytkownik kliknął.

Oto moje pytanie: jak zmusić UITextViewprzewijanie do góry za każdym razem, gdy zmieniam tekst? Próbowałem contentOffseti scrollRangeToVisible. Żadna praca.

Wszelkie sugestie będą mile widziane.

Odpowiedzi:


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

To robi to dla mnie.

Wersja Swift (Swift 4.1 z iOS 11 na Xcode 9.3):

note.setContentOffset(.zero, animated: true)

13
Nie pomagaj mi.
— Dmitry,

6
To działa, ale dlaczego jest to konieczne? Logika przewijania Apple wymaga dopracowania, jest zbyt wiele skoków po obręcz, aby rzeczy zachowywały się tak, jak powinny w większości przypadków.
— John Contarino

4
U mnie działa w iOS 9, ale nie wcześniej niż viewDidAppear().
— Murray Sagal

4
Możesz wywołać to wcześniej niż viewDidAppear (), wywołując to w
— viewDidLayoutSubviews

Dodałem widok tekstu w komórce tabeli, więc ustawiam to w komórce dla wiersza, ale nie działa
— Chandni

67

Jeśli ktoś ma ten problem w iOS 8, stwierdziliśmy, że po prostu ustawiając UITextView'swłaściwość text, a następnie dzwoni scrollRangeToVisibleze związkiem NSRangez location:0, length:0pracował. Mojego widoku tekstu nie można było edytować i przetestowałem zarówno możliwość wyboru, jak i niemożność wyboru (żadne ustawienie nie wpłynęło na wynik). Oto przykład języka Swift:

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

dzięki, żadne z powyższych nie działało dla mnie. Twoja próbka tak.
— user1244109

To działa, ale otrzymujemy animację przewijania, którą należy wyłączyć, wolę wyłączać przewijanie i ponownie włączać, jak sugerował
— @miguel

Cel-C: [myTextView scrollRangeToVisible: NSMakeRange (0, 0)];
— Stanowy

To już nie działa. iOS 9 i Xcode 7.3.1: /
— wasimsandhu,

Swift 3 Stll działa! :) po prostu skopiuj tę linię do swojej metody UITextFielDelegate. `func textViewDidEndEditing (_ textView: UITextView) {textView.scrollRangeToVisible (NSRange (lokalizacja: 0, długość: 0))}`
— lifeewithelliott

46

A oto moje rozwiązanie ...

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

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

5
Nie podoba mi się to rozwiązanie, ale przynajmniej od iOS 9 beta 5 to jedyna rzecz, która mi pasuje. Zakładam, że większość innych rozwiązań zakłada, że ​​widok tekstu jest już widoczny. W moim przypadku nie zawsze tak jest, więc muszę wyłączać przewijanie, dopóki się nie pojawi.
— robotspacer

1
To była jedyna rzecz, która też działała na mnie. iOS 9, przy użyciu nieedytowalnego widoku tekstu.
— SeanR

1
To działa dla mnie, nieedytowalny widok tekstu iOS 8 z przypisanym ciągiem. W widokuWillAppear - nie działa
— Tina Zh

Zgadzam się z @robotspacer. i to wciąż jedyne, które mi pasuje.
— lerp90

Dla mnie to rozwiązanie działało tylko z krótkimi (przypisanymi) tekstami. Przy dłuższych tekstach błąd przewijania UITextView wciąż pozostaje żywy :-) Moim (trochę brzydkim) rozwiązaniem było użycie funkcji dispatch_after z 2-sekundowym opóźnieniem, aby ponownie włączyć przewijanie. Wydaje się, że UITextView potrzebuje trochę czasu, aby wykonać swój układ tekstu ...
— LaborEtArs

38

Powołanie

scrollRangeToVisible(NSMakeRange(0, 0))

działa, ale zadzwoń

override func viewDidAppear(animated: Bool)

4
Przyznaję, że działało to tylko dla mnie w widoku Widoczny.
— Eric F.

2
Tylko to działa dla mnie. Dzięki. Ale jak wyłączyć animację, która jest dla mnie nieco denerwująca?
— rockhammer

Chociaż to działa, rozwiązanie I @Maria jest lepsze, ponieważ tam nie widać krótkiego „skoku”.
— Sipke Schoorstra

1
Cofam swój ostatni komentarz; to dla mnie najlepsza odpowiedź, ponieważ działa zarówno na iOS 10, jak i iOS 11, podczas gdy odpowiedź @ Marii nie działa dobrze na iOS 11.
— Sipke Schoorstra,

@SipkeSchoorstra, zobacz moją odpowiedź poniżej, aby działała bez żadnych skoków (przy użyciu KVO).
— Terry

29

Używałem atrybutu atrybutu w HTML z nieedytowalnym widokiem tekstu. U mnie też nie działa ustawienie przesunięcia zawartości. To zadziałało dla mnie: wyłącz przewijanie włączone, ustaw tekst, a następnie włącz ponownie przewijanie

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

Miałem textView wewnątrz tableViewCell. W tym przypadku textView został przewinięty do ok. 50%. To rozwiązanie jest tak proste, jak logiczne. Dzięki
— Julian F. Weinert

1
@ JulianF.Weinert Wygląda na to, że nie działa na iOS10. Mam taką samą sytuację jak ty z textView w tableViewCell. Nie używam możliwości edycji textView. Chcę tylko przewijać tekst. Ale textView jest zawsze przewijany do około 50%, jak powiedziałeś. Wypróbowałem również wszystkie inne sugestie dotyczące ustawiania setContentOffset i scrollRangeToVisible w tym poście. Żadna praca. Czy jest coś jeszcze, co mogłeś zrobić, aby to zadziałało?
— JeffB6688

@ JeffB6688 przepraszam, nie. To naprawdę dawno temu ... Może być kolejnym dziwactwem „nowej” wersji iOS?
— Julian F. Weinert

Ale niestety nie idealnie na iOS 11.2. Odpowiedź Onlu @RyanTCB działa zarówno na iOS 10, jak i iOS 11.
— Sipke Schoorstra,

Jak dotąd działa to dobrze na iOS 9, 10 i 11, ale musiałem to włączyć w viewDidAppearramach szerszej poważnej poprawki
— MadProgrammer

19

Ponieważ żadne z tych rozwiązań nie działało dla mnie i zmarnowałem zbyt dużo czasu na ich łączenie, to ostatecznie rozwiązało problem.

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

To naprawdę głupie, że nie jest to domyślne zachowanie dla tej kontrolki.

Mam nadzieję, że to pomoże komuś innemu.


2
To było bardzo pomocne! Musiałem też dodać: self.automaticallyAdjustsScrollViewInsets = NO; do widoku zawierającego UITextView, aby w pełni działał.
— jengelsma

I staje się on pracować w systemie SWIFT 3, z innym przesunięciem Calc self.textView.contentOffset.yopartego na tej odpowiedzi: stackoverflow.com/a/25366126/1736679
— Efren

Pracowałem dla mnie, gdy dostosowałem się do najnowszej składni dla Swift 3
— hawmack13

całkowicie się zgadzam, jakie to szalone? dzięki za rozwiązanie
— ajonno

17

Nie jestem pewien, czy rozumiem Twoje pytanie, ale czy próbujesz po prostu przewinąć widok do góry? Jeśli tak, powinieneś to zrobić

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


1
@SamStewart Link wydaje się być uszkodzony, zawsze prowadzi do strony moich kont (już zalogowanych). Czy możesz podać więcej informacji na temat oferowanych tam rozwiązań?
— uliwitness

@uliwitness To są teraz bardzo stare informacje, polecam wypróbowanie bardziej nowoczesnego zasobu zamiast 8-letniego postu. Interfejs API iOS zmienił się dramatycznie w ciągu tych 8 lat.
— Benjamin Sussman

1
Próbowałem znaleźć nowsze informacje. Jeśli wiesz, gdzie mogę to znaleźć, byłbym wdzięczny. Wciąż wydaje się, że jest to ten sam problem :(
— uliwitness

13

Dla iOS 10 i Swift 3 zrobiłem:

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

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

U mnie zadziałało, nie jestem pewien, czy masz problem z najnowszą wersją Swift i iOS.


Idealne rozwiązanie do naśladowania!
— iChirag

Mam problem z przewijaniem z UITextView w UIView kontrolowanym przez UIPageViewController. (Te strony pokazują moje ekrany Pomoc / Informacje.) Twoje rozwiązanie działa na każdej stronie oprócz pierwszej. Przeciągnięcie do dowolnej strony i powrót do pierwszej strony ustala pozycję przewijania tej pierwszej strony. Próbowałem dodać kolejny .isScrollEnabled = False do viewWillAppear, ale to nie przyniosło żadnego efektu. Zastanawiam się, dlaczego pierwsza strona zachowuje się inaczej niż reszta.
— Wayne Henderson,

[aktualizacja] Właśnie to rozwiązałem! Wprowadzenie opóźnienia wynoszącego 0,5 sekundy przed wyzwalaczami .isScrollEnabled = true rozwiązuje problem z pierwszym ładowaniem.
— Wayne Henderson,

bardzo dobrze . . .
— Maysam R

Działał jak urok. Jedyne rozwiązanie, które na mnie działa. Jesteś meżczyzną.
— Lazy Ninja

10

Mam zaktualizowaną odpowiedź, dzięki której widok tekstu będzie wyświetlany poprawnie i bez animacji przewijania przez użytkownika.

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

Dlaczego robisz w głównej kolejce, widok sinusoidalny WillAppear działa na głównej kolejce?
— karthikPrabhu Alagu

1
Zbyt wiele księżyców temu mi przypomnieć ale jest skierowana tutaj: stackoverflow.com/a/17490329/4750649
— MacInnis

8

Tak to działało w wydaniu iOS 9, ponieważ textView jest przewijany na górze przed pojawieniem się na ekranie

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

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

1
To załatwiło sprawę. Nawiasem mówiąc, nauczyło mnie to czegoś bardzo ważnego: „widok” w „widoku pojawi się” nie odnosi się tylko do self.view, ale najwyraźniej do WSZYSTKICH poglądów. Nie?
— Sjakelien

8

Tak to zrobiłem

Swift 4:

extension UITextView {

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

 }

To jedyna metoda, która daje najlepsze rezultaty. Głosuję za.
— smoothBlue

7

To zadziałało dla mnie

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

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

To jest bardzo dziwne. Na większości urządzeń zawijanie połączenia DispatchQueue.main.async(...nie jest konieczne, ale z jakiegoś powodu (np.) IPhone 5s (iOS 12.1) nie działa, dopóki nie wyślesz. Ale jak donosi stos wywołań w debugerze, we wszystkich przypadkach viewWillAppear() już działa w głównej kolejce ... - WTF, Apple ???
— Nicolas Miari

2
Zastanawiałem się również, dlaczego muszę jawnie wysyłać do głównej kolejki, mimo że uruchamiam wszystkie zdarzenia w głównej kolejce. Ale to zadziałało dla mnie
— Ankit garg

6

Spróbuj tego, aby przesunąć kursor na górę tekstu.

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

Zobacz, jak to idzie. Upewnij się, że zadzwonisz po tym, jak wszystkie wydarzenia zostały uruchomione lub cokolwiek robisz, zostanie zrobione.


próbował tego stary, bez powodzenia. Wydaje się, że jest to coś z pierwszymResponder. Jakieś inne sugestie?
— Sam Stewart

mam ten sam problem z pozycją kursora, działało u mnie ... świetne +1 za to.
— Dhaval

Mam animację od dołu do góry, ale nie potrzebowałem tego, więc jak to usunąć.
— Dhaval

2
Nie przewija się do góry (mniej na kilka pikseli).
— Dmitry,

6

Moim problemem jest ustawienie textView na przewijanie do góry, gdy pojawi się widok. W przypadku iOS 10.3.2 i Swift 3.

Rozwiązanie 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)
}

Rozwiązanie 2:

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

5

Połącz poprzednie odpowiedzi, teraz powinno działać:

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 jedyna rzecz, która działała na mnie, była głupia. Udało mi się scrollRangeToVisiblejednak usunąć .
— Jordan H,

Będziesz musiał zamienić NSRange (0,0) na NSMakeRange (0,0)
— RobP

5
[txtView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

Ta linia kodu działa dla mnie.


5

po prostu możesz użyć tego kodu

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

4

Odpowiedź Swift 2:

textView.scrollEnabled = false

/* Set the content of your textView here */

textView.scrollEnabled = true

Zapobiega to przewijaniu textView do końca tekstu po ustawieniu go.


4

W Swift 3:

  • viewWillLayoutSubviews to miejsce do wprowadzania zmian. viewWillAppear również powinien działać, ale logicznie układ powinien być wykonywany w viewWillLayoutSubviews .

  • Jeśli chodzi o metody, będą działać zarówno scrollRangeToVisible , jak i setContentOffset . Jednak setContentOffset umożliwia wyłączenie lub włączenie animacji.

Załóżmy, że UITextView nosi nazwę yourUITextView . Oto kod:

// 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 jest wywoływana przy każdej zmianie rozmiaru widoku. Generalnie nie tego chcesz.
— uliwitness

4

Musiałem wywołać następujący inside viewDidLayoutSubviews (wywołanie inside viewDidLoad było zbyt wczesne):

    myTextView.setContentOffset(.zero, animated: true)

nie działa w każdym scenariuszu
— Raja Saad

3

To zadziałało dla mnie. Opiera się na odpowiedzi RyanTCB, ale jest to wariant Objective-C tego samego rozwiązania:

- (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 jest wywoływana przy każdej zmianie rozmiaru widoku. Generalnie nie tego chcesz.
— uliwitness

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

- (void)viewWillAppear:(BOOL)animated{
[mytextView scrollRangeToVisible:NSMakeRange(0,0)];}
  • ViewWillappear zrobi to natychmiast, zanim użytkownik zauważy, ale ViewDidDisappear sekcja będzie to leniwie animować.
  • [mytextView setContentOffset:CGPointZero animated:YES]; czasami NIE działa (nie wiem dlaczego), więc używaj raczej scrollrangetovisible.

3

Rozwiązany problem: iOS = 10.2 Swift 3 UITextView

Właśnie użyłem następującej linii:

displayText.contentOffset.y = 0

1
Pracował (iOS 10.3)! Dodałem to w środku, viewDidLayoutSubviewswięc dzieje się tak, gdy ekran się obraca. W przeciwnym razie przesunięcie zawartości zostanie niewyrównane.
— Pup

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

Edytuj, podając więcej szczegółów.
— Schemat z

2

Miałem problem, ale w moim przypadku textview to podwidok jakiegoś niestandardowego widoku i dodałem go do ViewController. Żadne z rozwiązań nie działało dla mnie. Aby rozwiązać problem, dodano opóźnienie 0,1 sekundy, a następnie włączono przewijanie. U mnie to zadziałało.

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

Dla mnie dobrze działa ten kod:

    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

Aby przewinąć do dokładnej pozycji i pokazać ją na górze ekranu, używam tego kodu:

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

Ustawienie contentOffset.y powoduje przewijanie do końca tekstu, a następnie scrollRangeToVisible przewija do wartości scrollToLocation. W ten sposób potrzebna pozycja pojawia się w pierwszym wierszu scrollView.


1

U mnie w iOS 9 przestało działać dla mnie z przypisanym ciągiem za pomocą metody scrollRangeToVisible (1 wiersz był pogrubioną czcionką, inne wiersze były zwykłą czcionką)

Musiałem więc użyć:

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

gdzie opóźnienie to:

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

Spróbuj użyć tego rozwiązania 2-liniowego:

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

To zadziałało dla mnie. Mój UITextView znajdował się wewnątrz UICollectionVewCell. Podejrzewam, że scrollRangeToVisible przewija się w niewłaściwe miejsce, chyba że układ jest poprawnie obliczony
— John Fowler

1

Oto moje kody. To działa dobrze.

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()
      }
  }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.