Come scorrere fino alla fine di UITableView su iPhone prima che appaia la vista


136

Ho un UITableViewche è popolato con celle di altezza variabile. Vorrei che la tabella scorresse verso il basso quando la vista viene spostata in vista.

Al momento ho la seguente funzione

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

log è un array mutabile contenente gli oggetti che compongono il contenuto di ogni cella.

Il codice sopra funziona benissimo, viewDidAppeartuttavia questo ha lo sfortunato effetto collaterale di visualizzare la parte superiore della tabella quando appare la vista prima e poi saltare in fondo. Lo preferirei se table viewfosse possibile scorrere verso il basso prima che appaia.

Ho provato a scorrere viewWillAppeare, viewDidLoadin entrambi i casi, i dati non sono ancora stati caricati nella tabella ed entrambi generano un'eccezione.

Qualsiasi consiglio sarebbe molto apprezzato, anche se è solo un caso di dirmi quello che ho è tutto ciò che è possibile.

Risposte:


148

Credo che quella chiamata

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

farà quello che vuoi.


14
È perfetto grazie. Ho creato un CGPoint con un valore Y sufficientemente alto che lo farà visualizzare sempre il fondo. Una volta caricata la vista, posso usare (self.table.contentSize.height - self.table.frame.size.height) per spostarmi verso il basso con lo stesso metodo
acqu13sce

8
Anche se questa è la risposta perfetta, poiché non abbiamo bisogno di fare calcoli per quante celle, altezza della vista tabella ecc. MA, vorrei sottolineare che dobbiamo chiamarlo prima di ricaricare la vista tavolo ... Non funzionerà se noi scrivi questo dopo[table reloadData];
Fahim Parkar il

4
non funziona su iOS 10-12 - la tabella semplicemente scompare per la prima volta
Vyachaslav Gerchicov

2
O semplicemente scorrere fino a CGPoint(x: 0, y: tableView.contentSize.height)?
Ambra K,

5
Yikes !!! Fa svanire il tavolo Eeeek !!! Utilizzo ottimale di [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animato: NO];
Carl Hine,

122

Penso che il modo più semplice sia questo:

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

Versione Swift 3:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}

3
Non funziona se la cella è più alta di UITableView
Olav Gausaker il

3
La cella è più alta di UITableView? mai sentito un caso del genere.
Chamira Fernando,

@ChamiraFernando questo è il modo più semplice :)
AITAALI_ABDERRAHMANE

Si noti che potrebbe avere senso sostituire messages.countil già implementato myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Sì, è più lungo, ma potrebbe evitare la ripetizione del codice. È inoltre necessario gestire l'opzione facoltativa dataSource(non forzare lo scartamento come in questo esempio).
Nikolay Suvandzhiev,

@ChamiraFernando So che questa domanda è vecchia, ma solo perché non l'hai mai vista, non significa che non accada. Per rispondere alla tua domanda, app come Foursquare potrebbero presentare questa situazione, in cui l'utente scrive una recensione. L'altezza della cella è eccezionale rispetto all'altezza della vista tabella. È una situazione perfettamente soddisfacente.
Caio,

121

Dalla risposta di Jacob , questo è il codice:

- (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];
    }
}

Su iOS 11 dovresti usare l'altezza della cornice della vista del tavolo regolata:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav

41

Se è necessario scorrere fino alla fine EXACT del contenuto, è possibile farlo in questo modo:

- (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];
}

7
Funziona con autolayout, MA è importante chiamare questo metodo da viewDidLayoutSubviews
Omaty,

Potresti spiegare perché dobbiamo fare questo yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; :? Grazie.
Unheilig,

3
@Unheilig Se si scorre fino self.tableView.contentSize.heightal contenuto della vista tabella potrebbe non essere visibile, perché si scorre sotto il contenuto. Pertanto, è necessario scorrere fino a uno "spazio visibile della vista tabella" sopra la fine della vista tabella.
Hans One,

31

Sto usando il layout automatico e nessuna delle risposte ha funzionato per me. Ecco la mia soluzione che finalmente ha funzionato:

@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)];
    }
}

1
Questo funziona quasi per me, ma ottengo uno strano problema grafico mentre i dati della mia tabella vengono caricati da un'API esterna. Nel mio caso, devo chiamare setContentOffsetin un altro punto quando i dati sono stati recuperati e tableview ricaricato?
jmoz,

Prova a impostare l'offset in un gestore di completamento della tua richiesta.
RaffAl,

2
Questo non funziona in iOS 10 - mostra semplicemente una tabella con uno sfondo nero
RunLoop

2
Invece di usare CGFLOAT_MAXho usato contentSize.height - frame.height + contentInset.bottomquando si imposta l'offset del contenuto iniziale. L'uso CGFLOAT_MAXsembrava incasinato per me.
Baza207,

23

La soluzione accettata da @JacobRelkin non ha funzionato per me in iOS 7.0 usando Auto Layout.

Ho una sottoclasse personalizzata di UIViewControllere ho aggiunto una variabile di istanza _tableViewcome sua sottoview view. Ho posizionato _tableViewutilizzando Auto Layout. Ho provato a chiamare questo metodo alla fine viewDidLoade anche in viewWillAppear:. Né ha funzionato.

Quindi, ho aggiunto il seguente metodo alla mia sottoclasse personalizzata di UIViewController.

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

Chiamata [self tableViewScrollToBottomAnimated:NO]alla fine dei viewDidLoadlavori. Sfortunatamente, causa anche tableView:heightForRowAtIndexPath:di essere chiamato tre volte per ogni cella.


23

Ecco un'estensione che ho implementato in Swift 2.0. Queste funzioni dovrebbero essere chiamate dopo che tableviewè stato caricato:

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

3
Questo è meglio dell'uso delle dimensioni del contenuto. Per Swift3. if self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (riga: self.numberOfRows (inSection: 0) -1, sezione: 0), in: .bottom, animato: animato)}
Soohwan Park,

se scrollToLastRow questo metodo non funziona perfettamente, aggiungi solo self.layoutIfNeeded () ed è perfetto !!
Yogesh Patel

16

Dettagli

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

Codice

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

uso

tableView.scrollToBottom(animated: true)

Campione completo

Non dimenticare di incollare il codice della soluzione!

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

15

In realtà un modo "Swifter" per farlo rapidamente è:

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

lavoro Perfetto per me.


9

Volevo che la tabella si caricasse con la fine della tabella mostrata nella cornice. Ho trovato usando

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

non ha funzionato perché ha generato un errore quando l'altezza della tabella era inferiore all'altezza della cornice. Nota che la mia tabella ha solo una sezione.

La soluzione che ha funzionato per me è stata implementare il seguente codice in 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];
    }
}

Il BOOL ivar initialLoad è impostato su TRUE in viewDidLoad.


Devi chiamare scrollToRowAtIndexPathper niente? Stai già chiamando in setContentOffsetseguito, il che potrebbe rendere inutile quella prima chiamata.
Carlos P,

9

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


5

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

3
Questo non risponde alla domanda OP, questo è ciò che funzionava dall'inizio. Dovresti anche chiamare super.viewDidAppear
streem

4

È ovviamente un bug. Probabilmente da qualche parte nel codice che usi table.estimatedRowHeight = value(ad esempio 100). Sostituisci questo valore con il valore più alto che ritieni possa ottenere un'altezza di riga , ad esempio 500 .. Ciò dovrebbe risolvere il problema in combinazione con il seguente codice:

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

4

Dopo aver giocherellato un sacco, questo è ciò che ha funzionato per me:

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

La chiave si è rivelata non avvolgente scrollToRowAtIndexPathall'interno dispatch_asynccome alcuni hanno suggerito, ma semplicemente seguendolo con una chiamata a layoutIfNeeded.

La mia comprensione di ciò è che chiamare il metodo di scorrimento nel thread corrente garantisce che l'offset di scorrimento sia impostato immediatamente, prima che venga visualizzata la vista. Quando stavo inviando il thread principale, la vista veniva visualizzata per un istante prima che lo scorrimento diventasse effettivo.

(Inoltre, è necessario il viewHasAppearedflag perché non si desidera goToBottomogni volta che viewDidLayoutSubviewsviene chiamato. Viene chiamato ad esempio ogni volta che cambia l'orientamento.)


3

Utilizzando le soluzioni di cui sopra, questo scorrerà fino alla fine della tabella (solo se il contenuto della tabella viene caricato per primo):

//Scroll to bottom of table
CGSize tableSize = myTableView.contentSize;
[myTableView setContentOffset:CGPointMake(0, tableSize.height)];

3

In Swift 3.0

self.tableViewFeeds.setContentOffset(CGPoint(x: 0, y: CGFLOAT_MAX), animated: true)

2

Se devi caricare i dati in modo asincrono prima di scorrere verso il basso, ecco la possibile soluzione:

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

2

Funzione su rapido 3 scorrere verso il basso

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

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

dovresti aggiungere

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

o non scorrerà fino in fondo.


2

Usa questo semplice codice per scorrere tableView bottom

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}

1
Questo è fondamentalmente ciò che l'OP ha affermato di aver già provato. Questa risposta non affronta la domanda del PO su come farlo funzionare correttamente in viewWillAppear.
jk7,

2

Grazie Jacob per la risposta. davvero utile se qualcuno di interessante con la versione c # di monotouch

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

1

In iOS ha funzionato bene per me

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];

Deve essere chiamato da viewDidLayoutSubviews


1

[self.tableViewInfo scrollRectToVisible: CGRectMake (0, self.tableViewInfo.contentSize.height-self.tableViewInfo.height, self.tableViewInfo.width, self.tableViewInfo.height) animato: SÌ];


1

La risposta accettata non ha funzionato con la mia tabella (migliaia di righe, caricamento dinamico) ma il codice seguente funziona:

- (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];
    }
}

1

Non è necessario alcuno scorrimento, puoi semplicemente farlo usando questo codice:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];

1

Se si sta impostando il frame per tableview a livello di codice, assicurarsi di aver impostato correttamente il frame.


0

In Swift, hai solo bisogno

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

per farlo scorrere automaticamente verso il basso


0

In swift 3.0 Se vuoi andare su una particolare cella di tableview Cambia l'indice della cella Valore come cambia il valore "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)
    }
}

0

Credo che le vecchie soluzioni non funzionino con Swift3.

Se conosci le righe numeriche nella tabella puoi usare:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.