Jak przewinąć do dołu UITableView na iPhonie, zanim pojawi się widok

136

Mam, UITableViewktóry jest wypełniony komórkami o zmiennej wysokości. Chciałbym, aby tabela przewijała się na sam dół, gdy widok jest widoczny.

Obecnie mam następującą funkcję

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[log count]-1 inSection:0];
[self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

log to zmienna tablica zawierająca obiekty, które składają się na zawartość każdej komórki.

Powyższy kod działa dobrze, viewDidAppearale ma niefortunny efekt uboczny polegający na wyświetlaniu górnej części tabeli, gdy widok pojawia się po raz pierwszy, a następnie przeskakiwaniu na dół. Wolałbym, żeby table viewmożna było przewinąć do dołu, zanim się pojawi.

Próbowałem zwój viewWillAppeari viewDidLoadale w obu przypadkach dane nie zostały załadowane do tabeli jeszcze i zarówno wyjątek.

Wszelkie wskazówki byłyby bardzo mile widziane, nawet jeśli chodzi tylko o powiedzenie mi, co mam, to wszystko, co jest możliwe.

acqu13sce
źródło

Odpowiedzi:

148

Wierzę w to powołanie

 tableView.setContentOffset(CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude), animated: false)

zrobisz, co chcesz.

Jacob Relkin
źródło
14
Doskonale, dziękuję. Stworzyłem CGPoint z dostatecznie wysoką wartością Y, która sprawi, że będzie zawsze wyświetlał dno. Po załadowaniu widoku mogę użyć (self.table.contentSize.height - self.table.frame.size.height), aby przejść na dół tą samą metodą
acqu13sce
8
Chociaż jest to idealna odpowiedź, ponieważ nie musimy obliczać liczby komórek, wysokości widoku tabeli itp. ALE wskazałbym, że musimy to wywołać przed ponownym załadowaniem widoku tabeli ... To nie zadziała, jeśli będziemy napisz to później[table reloadData];
Fahim Parkar
4
nie działa na ios 10-12 - stół po prostu znika po raz pierwszy
Vyachaslav Gerchicov
2
Lub po prostu przewiń do CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K
5
Yikes !!! Sprawia, że ​​stół znika: -o. Najlepiej przy użyciu [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animated: NO];
Carl Hine
122

Myślę, że najłatwiej jest to:

if (self.messages.count > 0)
{
    [self.tableView 
        scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.messages.count-1 
        inSection:0] 
        atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

Wersja Swift 3:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}
Chamira Fernando
źródło
3
Nie działa, jeśli komórka jest wyższa niż UITableView
Olav Gausaker
3
Komórka jest wyższa niż UITableView? nigdy nie słyszałem takiego przypadku użycia.
Chamira Fernando
@ChamiraFernando to najłatwiejszy sposób :)
AITAALI_ABDERRAHMANE
Zauważ, że sensowne może być zastąpienie messages.countjuż zaimplementowanego myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Tak, jest dłuższy, ale może uniknąć powtarzania kodu. Musisz również obsłużyć opcjonalne dataSource(nie wymuszaj rozpakowywania, jak w tym przykładzie).
Nikolay Suvandzhiev
@ChamiraFernando Wiem, że to pytanie jest stare, ale to, że nigdy nie widziałeś, nie oznacza, że ​​się nie dzieje. Aby odpowiedzieć na Twoje pytanie, aplikacje takie jak Foursquare mogą mieć taką sytuację, w której użytkownik pisze recenzję. Wysokość komórki jest większa niż wysokość widoku tabeli. To całkiem dobra sytuacja.
Caio
121

Z odpowiedzi Jacoba , oto kod:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.messagesTableView.contentSize.height > self.messagesTableView.frame.size.height) 
    {
        CGPoint offset = CGPointMake(0, self.messagesTableView.contentSize.height - self.messagesTableView.frame.size.height);
        [self.messagesTableView setContentOffset:offset animated:YES];
    }
}
Osama F Elias
źródło
W systemie iOS 11 należy użyć dostosowanej wysokości ramki widoku tabeli:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav
41

Jeśli chcesz przewinąć do DOKŁADNEGO końca treści, możesz to zrobić w następujący sposób:

- (void)scrollToBottom
{
    CGFloat yOffset = 0;

    if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
        yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
    }

    [self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];
}
Hans One
źródło
7
Działa z autolayout, ALE ważne jest, aby wywołać tę metodę z
viewDidLayoutSubviews
Czy mógłbyś wyjaśnić, dlaczego musimy to zrobić yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; :? Dzięki.
Unheilig
3
@Unheilig Jeśli chcesz przewinąć do self.tableView.contentSize.heightzawartości tabeli, widok może nie być widoczny, ponieważ przewijasz poniżej treści. Dlatego musisz przewinąć do jednej „widocznej luki w widoku tabeli” nad końcem widoku tabeli.
Hans One
31

Używam autoukładu i żadna z odpowiedzi nie zadziałała. Oto moje rozwiązanie, które w końcu zadziałało:

@property (nonatomic, assign) BOOL shouldScrollToLastRow;


- (void)viewDidLoad {
    [super viewDidLoad];

    _shouldScrollToLastRow = YES;
}


- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Scroll table view to the last row
    if (_shouldScrollToLastRow)
    {
        _shouldScrollToLastRow = NO;
        [self.tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
    }
}
RaffAl
źródło
1
To prawie działa dla mnie, ale pojawia się dziwna usterka graficzna, podczas gdy moje dane tabeli są ładowane z zewnętrznego interfejsu API. Czy w moim przypadku muszę zadzwonić setContentOffsetw innym momencie, gdy dane zostały pobrane i ponownie załadowane widok tabeli?
jmoz
Spróbuj ustawić przesunięcie w module obsługi zakończenia żądania.
RaffAl
2
To nie działa w iOS 10 - po prostu pokazuje tabelę z czarnym tłem
RunLoop
2
Zamiast używać CGFLOAT_MAXużyłem contentSize.height - frame.height + contentInset.bottompodczas ustawiania początkowego przesunięcia zawartości. Używanie CGFLOAT_MAXwydawało mi się kłopotliwe.
Baza207
23

Przyjętego rozwiązania przez @JacobRelkin nie działa na mnie w iOS 7.0 przy użyciu automatycznego układu.

Mam niestandardową podklasę UIViewControlleri dodałem zmienną wystąpienia _tableViewjako podwidok jej view. Pozycjonowałem _tableViewużywając Auto Layout. Próbowałem nazwać tę metodę na końcu, viewDidLoada nawet w viewWillAppear:. Żadne nie działało.

Dlatego dodałem następującą metodę do mojej niestandardowej podklasy UIViewController.

- (void)tableViewScrollToBottomAnimated:(BOOL)animated {
    NSInteger numberOfRows = [_tableView numberOfRowsInSection:0];
    if (numberOfRows) {
        [_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:numberOfRows-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
    }
}

Dzwonię [self tableViewScrollToBottomAnimated:NO]pod koniec viewDidLoadprac. Niestety powoduje to również tableView:heightForRowAtIndexPath:trzykrotne wezwanie do każdej komórki.

ma11hew28
źródło
23

Oto rozszerzenie, które zaimplementowałem w Swift 2.0. Te funkcje należy wywołać po tableviewzaładowaniu:

import UIKit

extension UITableView {
    func setOffsetToBottom(animated: Bool) {
        self.setContentOffset(CGPointMake(0, self.contentSize.height - self.frame.size.height), animated: true)
    }

    func scrollToLastRow(animated: Bool) {
        if self.numberOfRowsInSection(0) > 0 {
            self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
        }
    }
}
Ryan Herubin
źródło
3
Jest to lepsze niż używanie rozmiaru zawartości. Dla Swift3. if self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (row: self.numberOfRows (inSection: 0) -1, section: 0), at: .bottom, animated: animated)}
Soohwan Park
jeśli scrollToLastRow ta metoda nie działa idealnie, po prostu dodaj self.layoutIfNeeded () i działa idealnie !!
Yogesh Patel
16

Detale

  • Xcode 8.3.2, swift 3.1
  • Xcode 10.2 (10E125), Swift 5

Kod

import UIKit

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

Stosowanie

tableView.scrollToBottom(animated: true)

Pełna próbka

Nie zapomnij wkleić kodu rozwiązania!

import UIKit

class ViewController: UIViewController {

    private weak var tableView: UITableView?
    private lazy var cellReuseIdentifier = "CellReuseIdentifier"

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: view.frame)
        view.addSubview(tableView)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        self.tableView = tableView
        tableView.dataSource = self
        tableView.performBatchUpdates(nil) { [weak self] result in
            if result { self?.tableView?.scrollToBottom(animated: true) }
        }
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath )
        cell.textLabel?.text = "\(indexPath)"
        return cell
    }
}
Wasilij Bodnarchuk
źródło
15

Właściwie „Szybszy” sposób na zrobienie tego w szybkim to:

var lastIndex = NSIndexPath(forRow: self.messages.count - 1, inSection: 0)
self.messageTableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)

praca Idealna dla mnie.

XcodeNOOB
źródło
9

Chciałem, aby stół ładował się końcem tabeli pokazanym w ramce. Znalazłem za pomocą

NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

nie zadziałał, ponieważ dawał błąd, gdy wysokość tabeli była mniejsza niż wysokość ramy. Zauważ, że mój stół ma tylko jedną sekcję.

Rozwiązaniem, które zadziałało, było zaimplementowanie następującego kodu w viewWillAppear:

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// on the initial cell load scroll to the last row (ie the latest Note)
if (initialLoad==TRUE) {
    initialLoad=FALSE; 
    NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
    [[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
        CGPoint offset = CGPointMake(0, (1000000.0));
        [self.tableView setContentOffset:offset animated:NO];
    }
}

BOOL ivar initialLoad jest ustawiony na TRUE w viewDidLoad.

TJ
źródło
Czy scrollToRowAtIndexPathw ogóle musisz dzwonić ? Dzwonisz już setContentOffsetpóźniej, co może sprawić, że ta pierwsza rozmowa będzie bezcelowa.
Carlos P
9

Dla Swift:

if tableView.contentSize.height > tableView.frame.size.height {
    let offset = CGPoint(x: 0, y: tableView.contentSize.height - tableView.frame.size.height)
    tableView.setContentOffset(offset, animated: false)
}
Segev
źródło
5

Zamiast tego powinieneś użyć UITableViewScrollPositionBottom.

Erphan Rajput
źródło
5

W przypadku Swift 3 (Xcode 8.1):

override func viewDidAppear(_ animated: Bool) {
    let numberOfSections = self.tableView.numberOfSections
    let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)

    let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
    self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
}
Irshad Qureshi
źródło
3
To nie odpowiada na pytanie OP, to działało od samego początku. Powinieneś także zadzwonić do super.viewDidAppear
streem
4

To oczywiście błąd. Prawdopodobnie gdzieś w kodzie, którego używasz table.estimatedRowHeight = value(na przykład 100). Zastąp tę wartość najwyższą wartością, jaką Twoim zdaniem może uzyskać wysokość wiersza , na przykład 500 .. To powinno rozwiązać problem w połączeniu z następującym kodem:

//auto scroll down example
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

dispatch_after(time, dispatch_get_main_queue(), {
    self.table.scrollToRowAtIndexPath(NSIndexPath(forRow: self.Messages.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
})
Mohsen Karbassi
źródło
4

Po wielu zabawach to zadziałało:

var viewHasAppeared = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if !viewHasAppeared { goToBottom() }
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    viewHasAppeared = true
}

private func goToBottom() {
    guard data.count > 0 else { return }
    let indexPath = NSIndexPath(forRow: data.count - 1, inSection: 0)
    tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)
    tableView.layoutIfNeeded()
}

Kluczem okazała się być nie owijając scrollToRowAtIndexPathwnętrze dispatch_asyncjak niektórzy sugerowali, ale po prostu po to wezwanie dolayoutIfNeeded .

Rozumiem to w ten sposób, że wywołanie metody scroll w bieżącym wątku gwarantuje, że przesunięcie przewijania zostanie ustawione bezpośrednio, przed wyświetleniem widoku. Kiedy wysyłałem wiadomość do głównego wątku, widok był wyświetlany na chwilę, zanim zwój zaczął działać.

(Pamiętaj też, że potrzebujesz viewHasAppearedflagi, ponieważ nie chcesz za goToBottomkażdym razem viewDidLayoutSubviewswywoływać. Jest wywoływana na przykład za każdym razem, gdy zmienia się orientacja).

skot
źródło
3

Korzystając z powyższych rozwiązań, spowoduje to przewinięcie do dołu tabeli (tylko jeśli zawartość tabeli jest ładowana jako pierwsza):

//Scroll to bottom of table
CGSize tableSize = myTableView.contentSize;
[myTableView setContentOffset:CGPointMake(0, tableSize.height)];
JimmyJammed
źródło
3

W Swift 3.0

self.tableViewFeeds.setContentOffset(CGPoint(x: 0, y: CGFLOAT_MAX), animated: true)
Amit Verma
źródło
2

Jeśli musisz załadować dane asynchronicznie przed przewinięciem w dół, oto możliwe rozwiązanie:

tableView.alpha = 0 // We want animation!
lastMessageShown = false // This is ivar

viewModel.fetch { [unowned self] result in
    self.tableView.reloadData()

    if !self.lastMessageShown {
        dispatch_async(dispatch_get_main_queue()) { [unowned self] in
            if self.rowCount > 0 {
                self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.rowCount, inSection: 0), atScrollPosition: .Bottom, animated: false)
            }

            UIView.animateWithDuration(0.1) {
                self.tableView.alpha = 1
                self.lastMessageShown = true // Do it once
            }
        }
    }
}
SoftDesigner
źródło
2

Funkcja w Swift 3 przewiń do dołu

 override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(false)
        //scroll down
        if lists.count > 2 {
            let numberOfSections = self.tableView.numberOfSections
            let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)
            let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
            self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
        }
    }
Tarik
źródło
2
func scrollToBottom() {

    let sections = self.chatTableView.numberOfSections

    if sections > 0 {

        let rows = self.chatTableView.numberOfRows(inSection: sections - 1)

        let last = IndexPath(row: rows - 1, section: sections - 1)

        DispatchQueue.main.async {

            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }
    }
}

należy dodać

DispatchQueue.main.async {
            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }

lub nie przewinie się do dołu.


źródło
2

Użyj tego prostego kodu, aby przewinąć tableView bottom

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}
Bibin Joseph
źródło
1
To jest w zasadzie to, co powiedział OP, że już próbował. Ta odpowiedź nie odpowiada na pytanie OP, jak sprawić, by działał poprawnie w viewWillAppear.
jk7
2

Dzięki Jacob za odpowiedź. bardzo pomocne, jeśli ktoś jest zainteresowany wersją monotouch c #

private void SetScrollPositionDown() {
    if (tblShoppingListItem.ContentSize.Height > tblShoppingListItem.Frame.Size.Height) {
        PointF offset = new PointF(0, tblShoppingListItem.ContentSize.Height - tblShoppingListItem.Frame.Size.Height);
        tblShoppingListItem.SetContentOffset(offset,true );
    }
}
Mahesh
źródło
1

W iOS działało to dobrze dla mnie

CGFloat height = self.inputTableView.contentSize.height;
if (height > CGRectGetHeight(self.inputTableView.frame)) {
    height -= (CGRectGetHeight(self.inputTableView.frame) - CGRectGetHeight(self.navigationController.navigationBar.frame));
}
else {
    height = 0;
}
[self.inputTableView setContentOffset:CGPointMake(0, height) animated:animated];

Musi zostać wywołany z viewDidLayoutSubviews

Josip B.
źródło
1

[self.tableViewInfo scrollRectToVisible: CGRectMake (0, self.tableViewInfo.contentSize.height-self.tableViewInfo.height, self.tableViewInfo.width, self.tableViewInfo.height) animated: YES];

leetvin
źródło
1

Zaakceptowana odpowiedź nie działa z moją tabelą (tysiące wierszy, dynamiczne ładowanie), ale poniższy kod działa:

- (void)scrollToBottom:(id)sender {
    if ([self.sections count] > 0) {
        NSInteger idx = [self.sections count] - 1;
        CGRect sectionRect = [self.tableView rectForSection:idx];
        sectionRect.size.height = self.tableView.frame.size.height;
        [self.tableView scrollRectToVisible:sectionRect animated:NO];
    }
}
user3246173
źródło
1

Nie ma potrzeby przewijania, możesz to zrobić za pomocą tego kodu:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
Anuj Kumar Rai
źródło
1

Jeśli konfigurujesz ramkę dla widoku tabeli programowo, upewnij się, że ustawiasz ramkę poprawnie.

Narasimha Nallamsetty
źródło
0

W Swift po prostu potrzebujesz

self.tableView.scrollToNearestSelectedRowAtScrollPosition(UITableViewScrollPosition.Bottom, animated: true)

aby automatycznie przewijał się do przycisku

Nawet Cheng
źródło
0

W swift 3.0 Jeśli chcesz przejść do dowolnej konkretnej komórki widoku tabeli Zmień indeks komórki Wartość, na przykład zmień wartość „self.yourArr.count”.

self.yourTable.reloadData()
self.scrollToBottom() 
func scrollToBottom(){
    DispatchQueue.global(qos: .background).async {
        let indexPath = IndexPath(row: self.yourArr.count-1, section: 0)
        self.tblComment.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}
Amit Verma
źródło
0

Uważam, że stare rozwiązania nie działają ze swift3.

Jeśli znasz liczbę wierszy w tabeli, możesz użyć:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
ymutlu
źródło