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.