UIScrollView przewiń programowo do dołu

298

Jak mogę UIScrollViewprzewinąć do dołu w kodzie? Lub w bardziej ogólny sposób, do dowolnego punktu widzenia?

Nico
źródło

Odpowiedzi:

626

Możesz użyć funkcji UIScrollView, setContentOffset:animated:aby przewinąć do dowolnej części widoku zawartości. Oto kod, który przewinąłby do dołu, zakładając, że scrollView to self.scrollView:

CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];

Mam nadzieję, że to pomaga!

Ben Gotow
źródło
25
prawdopodobnie chcesz, aby przewinąć do dołu zamiast z widoku przewijania: CGPoint bottomOffset = CGPointMake (0, [self.scrollView contentSize] .height - self.scrollView.frame.size.height);
Grantland Chew
70
Nie bierzesz pod uwagę wypustek. Myślę, że powinieneś preferować ten bottomOffset:CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
Martin
1
to nie działa w trybie poziomym! nie do końca przewiń w dół
Mo Farhand,
1
Nie będzie działać poprawnie, jeśli contentSizejest niższy niż bounds. Tak powinno być:scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk
1
To obsługuje dolną wstawkę i wykracza poza 0:let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)
Sensowne
136

Szybka wersja zaakceptowanej odpowiedzi do łatwego wklejania kopii:

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)
Esqarrouth
źródło
3
bottomOffset powinno być dozwolone, a nie zmienne w twoim przykładzie.
Hobbes the Tige,
prawda, zmieniłem to. swift2 rzeczy :)
Esqarrouth,
9
musisz sprawdzić, czy scrollView.contentSize.height - scrollView.bounds.size.height> 0 w przeciwnym razie będziesz mieć dziwne wyniki
iluvatar_GR
2
To rozwiązanie nie jest idealne, gdy masz nowy pasek nawigacyjny.
Swift Rabbit
@Josh, Wciąż czekam na dzień SO dowiaduje się o tym
Alexandre G
53

Najprostsze rozwiązanie:

[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
Hai Hw
źródło
Dzięki. Zapomniałem, że zmieniłem mój scrollview na UITableView i ten kod działał również dla UITableView, FYI.
LevinsonTechnologies
1
Dlaczego nie tylko: [scrollview scrollRectToVisible: CGRectMake (0, scrollview.contentSize.height, 1, 1) animowany: TAK];
Johannes,
29

Tylko rozszerzenie istniejącej odpowiedzi.

CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];

Dba również o dolną wstawkę (na wypadek, gdybyś używał jej do dostosowania widoku przewijania, gdy klawiatura jest widoczna)

vivek241
źródło
To powinna być poprawna odpowiedź ... nie te, które się złamią, jeśli kiedykolwiek pojawią się klawisze.
Ben Guild
19

Ustawienie przesunięcia zawartości do wysokości rozmiaru zawartości jest nieprawidłowe: przewija dolną część zawartości do góry widoku przewijania, a tym samym poza zasięg wzroku.

Prawidłowym rozwiązaniem jest przewinięcie dolnej części zawartości do dolnej części przewijanego widoku, tak jak to ( svto UIScrollView):

CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
if (sv.contentOffset.y + bsz.height > csz.height) {
    [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                     csz.height - bsz.height) 
                animated:YES];
}
matowy
źródło
1
Nie powinno tak być if (sv.contentOffset.y + csz.height > bsz.height) {?
i_am_jorf
@jeffamaphone - Dziękujemy za pytanie. Właściwie w tym momencie nie jestem nawet pewien, w jakim celu ten warunek miał służyć! To setContentOffset:polecenie jest ważne.
mat.
Zakładam, że omija połączenie setContentOffset, jeśli nic nie zamierza zrobić?
i_am_jorf
@jeffamaphone - Kiedyś po obróceniu urządzenia widok przewijania mógł kończyć się treścią na dole wyższą niż dół ramki (ponieważ jeśli obrócimy widok przewijania, jego przesunięcie zawartości pozostanie stałe, ale wysokość widoku przewijania może wzrosnąć z powodu do automatycznej zmiany rozmiaru). Warunek mówi: Jeśli tak się stanie, odłóż zawartość z powrotem na dół na dole ramki. Ale głupio z mojej strony było uwzględnienie tego warunku, kiedy wkleiłem kod. Jednak warunek jest poprawnie testowany pod kątem tego, dla czego był testowany.
mat
19

Szybka implementacja:

extension UIScrollView {
   func scrollToBottom(animated: Bool) {
     if self.contentSize.height < self.bounds.size.height { return }
     let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height)
     self.setContentOffset(bottomOffset, animated: animated)
  }
}

Użyj tego:

yourScrollview.scrollToBottom(animated: true)
duan
źródło
15

Biorąc contentInsetpod uwagę rozwiązanie Swift 2.2

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)

To powinno być rozszerzenie

extension UIScrollView {

  func scrollToBottom() {
    let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
    setContentOffset(bottomOffset, animated: true)
  }
}

Pamiętaj, że możesz sprawdzić, czy bottomOffset.y > 0przed przewinięciem

onmyway133
źródło
14

Co jeśli contentSizejest niższy niż bounds?

Dla Swift jest to:

scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk
źródło
13

Przewiń na górę

- CGPoint topOffset = CGPointMake(0, 0);
- [scrollView setContentOffset:topOffset animated:YES];

Przewiń do dołu

- CGPoint bottomOffset = CGPointMake(0, scrollView.contentSize.height - self.scrollView.bounds.size.height);
 - [scrollView setContentOffset:bottomOffset animated:YES];
Tahir Waseem
źródło
7

Znalazłem również inny użyteczny sposób na zrobienie tego w przypadku korzystania z UITableview (który jest podklasą UIScrollView):

[(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Nico
źródło
Do Twojej wiadomości, to tylko
funkcja
7

Korzystanie z setContentOffset:animated:funkcji UIScrollView do przewijania w dół w Swift.

let bottomOffset : CGPoint = CGPointMake(0, scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
Kim
źródło
5

Wygląda na to, że wszystkie odpowiedzi tutaj nie uwzględniały bezpiecznego obszaru. Od iOS 11 iPhone X miał bezpieczny obszar. Może to wpłynąć na scrollViewcontentInset .

W przypadku iOS 11 i nowszych, aby poprawnie przewinąć do dołu z dołączoną wstawką zawartości. Powinieneś użyć adjustedContentInsetzamiast contentInset. Sprawdź ten kod:

  • Szybki:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
  • Cel C
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
  • Szybkie rozszerzenie (zachowuje oryginał contentOffset.x):
extension UIScrollView {
    func scrollsToBottom(animated: Bool) {
        let bottomOffset = CGPoint(x: contentOffset.x,
                                   y: contentSize.height - bounds.height + adjustedContentInset.bottom)
        setContentOffset(bottomOffset, animated: animated)
    }
}

Bibliografia:

Honghao Zhang
źródło
5

W przypadku (opcjonalnie) footerViewi contentInsetrozwiązaniem jest:

CGPoint bottomOffset = CGPointMake(0, _tableView.contentSize.height - tableView.frame.size.height + _tableView.contentInset.bottom);
if (bottomOffset.y > 0) [_tableView setContentOffset: bottomOffset animated: YES];
Pieter
źródło
4

Szybki:

Możesz użyć takiego rozszerzenia:

extension UIScrollView {
    func scrollsToBottom(animated: Bool) {
        let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
        setContentOffset(bottomOffset, animated: animated)
    }
}

Posługiwać się:

scrollView.scrollsToBottom(animated: true)
Mauro García
źródło
3

valdyr, mam nadzieję, że to ci pomoże:

CGPoint bottomOffset = CGPointMake(0, [textView contentSize].height - textView.frame.size.height);

if (bottomOffset.y > 0)
 [textView setContentOffset: bottomOffset animated: YES];
Misza
źródło
2

Kategoria na ratunek!

Dodaj to do współdzielonego nagłówka narzędzia gdzieś:

@interface UIScrollView (ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated;
@end

A następnie do implementacji tego narzędzia:

@implementation UIScrollView(ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated
{
     CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
     [self setContentOffset:bottomOffset animated:animated];
}
@end

Następnie zaimplementuj go w dowolnym miejscu, na przykład:

[[myWebView scrollView] scrollToBottomAnimated:YES];
BadPirate
źródło
Zawsze, zawsze, zawsze, zawsze używaj kategorii! Idealnie :)
Fattie
2

Dla widoku poziomego ScrollView

Jeśli podoba mi się, że ma widok poziomy ScrollView i chcesz przewinąć do jego końca (w moim przypadku do jego prawej strony), musisz zmienić niektóre części akceptowanej odpowiedzi:

Cel C

CGPoint rightOffset = CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, 0 );
[self.scrollView setContentOffset:rightOffset animated:YES];

Szybki

let rightOffset: CGPoint = CGPoint(x: self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, y: 0)
self.scrollView.setContentOffset(rightOffset, animated: true)
Esmaeil
źródło
2

Jeśli w jakiś sposób zmienisz scrollView contentSize (np. Dodasz coś do stackView, który znajduje się w scrollView), musisz wywołać scrollView.layoutIfNeeded()przed przewinięciem , w przeciwnym razie nic nie zrobi.

Przykład:

scrollView.layoutIfNeeded()
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
if(bottomOffset.y > 0) {
    scrollView.setContentOffset(bottomOffset, animated: true)
}
Robert Koval
źródło
1

Dobrym sposobem na zapewnienie widoczności dolnej części treści jest użycie formuły:

contentOffsetY = MIN(0, contentHeight - boundsHeight)

Dzięki temu dolna krawędź treści zawsze znajduje się na dolnej krawędzi widoku lub powyżej niej. Jest MIN(0, ...)to wymagane, ponieważ UITableView(i prawdopodobnie UIScrollView) zapewnia, contentOffsetY >= 0gdy użytkownik próbuje przewijać poprzez widoczne przyciąganie contentOffsetY = 0. Wygląda to dość dziwnie dla użytkownika.

Kod do wdrożenia to:

UIScrollView scrollView = ...;
CGSize contentSize = scrollView.contentSize;
CGSize boundsSize = scrollView.bounds.size;
if (contentSize.height > boundsSize.height)
{
    CGPoint contentOffset = scrollView.contentOffset;
    contentOffset.y = contentSize.height - boundsSize.height;
    [scrollView setContentOffset:contentOffset animated:YES];
}
jjrscott
źródło
1

Jeśli nie potrzebujesz animacji, działa to:

[self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];
Krys Jurgowski
źródło
1

Chociaż Mattrozwiązanie wydaje mi się poprawne, należy uwzględnić również wstawkę widoku kolekcji, jeśli istnieje taka, która została skonfigurowana.

Dostosowany kod będzie:

CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
NSInteger bottomInset = sv.contentInset.bottom;
if (sv.contentOffset.y + bsz.height + bottomInset > csz.height) {
    [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                     csz.height - bsz.height + bottomInset) 
                animated:YES];
}
tiguero
źródło
1

W krótkim:

   if self.mainScroll.contentSize.height > self.mainScroll.bounds.size.height {
        let bottomOffset = CGPointMake(0, self.mainScroll.contentSize.height - self.mainScroll.bounds.size.height);
        self.mainScroll.setContentOffset(bottomOffset, animated: true)
    }
Ponja
źródło
1

Rozwiązanie, aby przewinąć do ostatniego elementu tabeli Widok:

Swift 3:

if self.items.count > 0 {
        self.tableView.scrollToRow(at:  IndexPath.init(row: self.items.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: true)
}
MarionFlex
źródło
0

Nie działa na mnie, gdy próbowałem go używać w UITableViewControllersprawie self.tableView (iOS 4.1), po dodaniufooterView . Przewija się poza granice, pokazując czarny ekran.

Alternatywne rozwiązanie:

 CGFloat height = self.tableView.contentSize.height; 

 [self.tableView setTableFooterView: myFooterView];
 [self.tableView reloadData];

 CGFloat delta = self.tableView.contentSize.height - height;
 CGPoint offset = [self.tableView contentOffset];
 offset.y += delta;

 [self.tableView setContentOffset: offset animated: YES];
valdyr
źródło
0
CGFloat yOffset = scrollView.contentOffset.y;

CGFloat height = scrollView.frame.size.height;

CGFloat contentHeight = scrollView.contentSize.height;

CGFloat distance = (contentHeight  - height) - yOffset;

if(distance < 0)
{
    return ;
}

CGPoint offset = scrollView.contentOffset;

offset.y += distance;

[scrollView setContentOffset:offset animated:YES];
8suhas
źródło
0

Przekonałem się, że contentSizetak naprawdę nie odzwierciedla faktycznego rozmiaru tekstu, więc przy próbie przewinięcia w dół będzie to nieco odsuwane. Najlepszym sposobem, aby określić rzeczywisty rozmiar zawartość jest rzeczywiście korzystać z NSLayoutManager„s usedRectForTextContainer:metody:

UITextView *textView;
CGSize textSize = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size;

Aby ustalić, ile tekstu faktycznie ma być wyświetlany UITextView, możesz go obliczyć, odejmując wstawki kontenera tekstu od wysokości ramki.

UITextView *textView;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;

Następnie łatwo jest przewijać:

// if you want scroll animation, use contentOffset
UITextView *textView;
textView.contentOffset = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);

// if you don't want scroll animation
CGRect scrollBounds = textView.bounds;
scrollBounds.origin = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
textView.bounds = scrollBounds;

Niektóre liczby w celach informacyjnych dotyczące różnych rozmiarów pustych UITextView.

textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)
mikeho
źródło
0

Rozszerz UIScrollView, aby dodać metodę scrollToBottom:

extension UIScrollView {
    func scrollToBottom(animated:Bool) {
        let offset = self.contentSize.height - self.visibleSize.height
        if offset > self.contentOffset.y {
            self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
        }
    }
}
jeune-loup
źródło
Co to dodaje do istniejących odpowiedzi?
Greybeard
-1

Wersja akceptowanej odpowiedzi Xamarin.iOS

var bottomOffset = new CGPoint (0,
     scrollView.ContentSize.Height - scrollView.Frame.Size.Height
     + scrollView.ContentInset.Bottom);

scrollView.SetContentOffset (bottomOffset, false);
Abdur
źródło
-1

Wersja Xamarin.iOS dla UICollectionViewzaakceptowanej odpowiedzi ułatwiająca kopiowanie i wklejanie

var bottomOffset = new CGPoint (0, CollectionView.ContentSize.Height - CollectionView.Frame.Size.Height + CollectionView.ContentInset.Bottom);          
CollectionView.SetContentOffset (bottomOffset, false);
Abdur
źródło