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.

Sam Stewart
źródło

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)
Wayne Lo
źródło
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))
nerd2know
źródło
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
    }
miguel
źródło
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)
RyanTCB
źródło
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];
Maria
źródło
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.

Drew S.
źródło
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];

Benjamin Sussman
źródło
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.

ChallengerGuy
źródło
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))
    })
MacInnis
źródło
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;
}
ageorgios
źródło
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)
    }

 }
Maksym Horbenko
źródło
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))
    }
  }
Ankit garg
źródło
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.

John Ballinger
źródło
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
}
AechoLiu
źródło
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
Géza Mikló
źródło
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.

Patrycja
źródło
5

po prostu możesz użyć tego kodu

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    textView.scrollRangeToVisible(NSMakeRange(0, 0))
}
erkancan
źródło
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.

lukrowany
źródło
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)
}
Marco Leong
źródło
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)
Charlie Seligman
źródło
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)];
}
Hermann Klecker
źródło
3
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [textview setContentOffset:CGPointZero animated:NO];
}
Albert Zou
źródło
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.
Kahng
źródło
3

Rozwiązany problem: iOS = 10.2 Swift 3 UITextView

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

displayText.contentOffset.y = 0
Stephen Rowe
źródło
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];
Shyla
źródło
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
}
SaRaVaNaN DM
źródło
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.

Igor
źródło
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)
}
Paul T.
źródło
1

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

view.layoutIfNeeded()
textView.contentOffset = CGPointMake(textView.contentInset.left, textView.contentInset.top)
Nikolay Shubenkov
źródło
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()
      }
  }
David.Chu.ca
źródło