Cómo desplazarse hasta la parte inferior de un UITableView en el iPhone antes de que aparezca la vista

136

Tengo un UITableViewque se rellena con celdas de altura variable. Me gustaría que la tabla se desplace hacia abajo cuando la vista aparece.

Actualmente tengo la siguiente función

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

log es una matriz mutable que contiene los objetos que componen el contenido de cada celda.

El código anterior funciona bien, viewDidAppearsin embargo, esto tiene el desafortunado efecto secundario de mostrar la parte superior de la tabla cuando aparece la vista por primera vez y luego saltar a la parte inferior. Preferiría que se table viewpudiera desplazar hasta el fondo antes de que aparezca.

Probé el desplazamiento viewWillAppeary, viewDidLoaden ambos casos, los datos aún no se han cargado en la tabla y ambos arrojan una excepción.

Cualquier orientación sería muy apreciada, incluso si solo se trata de decirme que lo que tengo es todo lo que es posible.

absolver
fuente

Respuestas:

148

Creo que llamar

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

Hará lo que quieras.

Jacob Relkin
fuente
14
Eso es perfecto, gracias. Creé un CGPoint con un valor Y suficientemente alto que hará que siempre muestre la parte inferior. Una vez que la vista se ha cargado, puedo usar (self.table.contentSize.height - self.table.frame.size.height) para desplazarme hacia la parte inferior con el mismo método
acceda
8
Aunque esta es la respuesta perfecta, ya que no necesitamos hacer un cálculo para cuántas celdas, altura de la vista de tabla, etc. PERO quisiera señalar que debemos llamar a esto antes de volver a cargar la vista de tabla ... No funcionará si escribe esto después[table reloadData];
Fahim Parkar
44
no funciona en iOS 10-12 - la tabla simplemente desaparece por primera vez
Vyachaslav Gerchicov
2
O simplemente desplazarse a CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K
55
¡Ay! Hace que la mesa desaparezca: -o. Mejor usando [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animated: NO];
Carl Hine
122

Creo que la forma más fácil es esta:

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

Versión Swift 3:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}
Chamira Fernando
fuente
3
No funciona si la celda es más alta que la UITableView
Olav Gausaker
3
¿La celda es más alta que UITableView? Nunca escuché tal caso de uso.
Chamira Fernando
@ChamiraFernando esta es la forma más fácil :)
AITAALI_ABDERRAHMANE
Tenga en cuenta que puede tener sentido reemplazar el messages.countpor el ya implementado myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Sí, es más largo, pero podría evitar la repetición del código. También debe manejar lo opcional dataSource(no fuerce el desenvolvimiento como en esta muestra).
Nikolay Suvandzhiev
@ChamiraFernando Sé que esta pregunta es antigua, pero solo porque nunca la viste, no significa que no suceda. Para responder a su pregunta, las aplicaciones como Foursquare pueden tener esta situación, donde el usuario escribe una reseña. La altura de la celda es mayor que la altura de la vista de tabla. Es una situación perfectamente buena.
Caio
121

De la respuesta de Jacob , este es el código:

- (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
fuente
En iOS 11, debe usar la altura del marco de la vista de tabla ajustada:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav
41

Si necesita desplazarse al extremo EXACTO del contenido, puede hacerlo así:

- (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
fuente
77
Funciona con autolayout, PERO es importante llamar a este método desde viewDidLayoutSubviews
Omaty del
¿Podría explicar por qué necesitamos hacer esto yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ? Gracias.
Unheilig
3
@Unheilig Si desea desplazarse al self.tableView.contentSize.heightcontenido de la vista de tabla puede no estar visible, porque se desplaza debajo del contenido. Por lo tanto, debe desplazarse a un "espacio de vista de tabla visible" sobre el final de la vista de tabla.
Hans One
31

Estoy usando autolayout y ninguna de las respuestas funcionó para mí. Aquí está mi solución que finalmente funcionó:

@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
fuente
1
Esto casi funciona para mí, pero obtengo un error gráfico extraño mientras los datos de mi tabla se cargan desde una API externa. En mi caso, ¿necesito llamar setContentOffseten otro momento cuando se recuperaron los datos y se volvió a cargar la vista de tabla?
jmoz
Intente configurar el desplazamiento en un controlador de finalización de su solicitud.
RaffAl
2
Esto no funciona en iOS 10 - simplemente muestra una tabla con un fondo negro
RunLoop
2
En lugar de usar CGFLOAT_MAX, usé contentSize.height - frame.height + contentInset.bottomal configurar el desplazamiento de contenido inicial. Usar me CGFLOAT_MAXpareció un desastre.
Baza207
23

La solución aceptada por @JacobRelkin no funcionó para mí en iOS 7.0 usando Auto Layout.

Tengo una subclase personalizada de UIViewControllery agregué una variable de instancia _tableViewcomo una subvista de su view. Me posicioné _tableViewusando Auto Layout. Intenté llamar a este método al final viewDidLoade incluso en viewWillAppear:. Ninguno funcionó.

Entonces, agregué el siguiente método a mi subclase personalizada de UIViewController.

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

Llamando [self tableViewScrollToBottomAnimated:NO]al final de las viewDidLoadobras. Desafortunadamente, también hace tableView:heightForRowAtIndexPath:que lo llamen tres veces por cada célula.

ma11hew28
fuente
23

Aquí hay una extensión que implementé en Swift 2.0. Estas funciones deben invocarse después de que tableviewse haya cargado:

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
fuente
3
Esto es mejor que usar el tamaño del contenido. Para Swift 3. if self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (fila: self.numberOfRows (inSection: 0) -1, sección: 0), en: .bottom, animado: animado)}
Soohwan Park
si scrollToLastRow este método no funciona perfectamente, simplemente agregue self.layoutIfNeeded () y ¡funciona perfecto!
Yogesh Patel
16

Detalles

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

Código

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)

Muestra completa

¡No olvide pegar el código de la solución!

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
    }
}
Vasily Bodnarchuk
fuente
15

En realidad, una forma "más rápida" de hacerlo con rapidez es:

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

trabajo perfecto para mi.

XcodeNOOB
fuente
9

Quería que la tabla se cargara con el final de la tabla que se muestra en el marco. Encontré usando

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

no funcionó porque dio un error cuando la altura de la mesa era menor que la altura del marco. Tenga en cuenta que mi tabla solo tiene una sección.

La solución que funcionó para mí fue implementar el siguiente código en 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 se establece en TRUE en viewDidLoad.

TJ
fuente
¿Necesitas llamar scrollToRowAtIndexPath? Ya estás llamando setContentOffsetdespués, lo que podría hacer que esa primera llamada no tenga sentido.
Carlos P
9

Para 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
fuente
5

Deberías usar UITableViewScrollPositionBottomen su lugar.

Erphan Rajput
fuente
5

Para 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
fuente
3
Esto no responde a la pregunta de OP, esto es lo que funcionaba desde el principio. También debe llamar a super.viewDidAppear
streem
4

Es, por supuesto, un error. Probablemente en algún lugar de su código que use table.estimatedRowHeight = value(por ejemplo, 100). Reemplace este valor por el valor más alto que cree que podría obtener una altura de fila , por ejemplo 500 .. Esto debería resolver el problema en combinación con el siguiente código:

//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
fuente
4

Después de mucho violín, esto es lo que funcionó para mí:

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 clave resultó no estar envuelta en el scrollToRowAtIndexPathinterior dispatch_asynccomo algunos han sugerido, sino simplemente seguirla con una llamada a layoutIfNeeded.

Según tengo entendido, llamar al método de desplazamiento en el hilo actual garantiza que el desplazamiento de desplazamiento se establezca inmediatamente, antes de que se muestre la vista. Cuando estaba enviando al hilo principal, la vista se mostraba por un instante antes de que el desplazamiento entrara en vigor.

(También NB necesita la viewHasAppearedbandera porque no desea que se llame goToBottomcada vez viewDidLayoutSubviews. Se llama, por ejemplo, cada vez que cambia la orientación).

skot
fuente
3

Usando las soluciones anteriores, esto se desplazará hasta la parte inferior de su tabla (solo si el contenido de la tabla se carga primero):

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

En Swift 3.0

self.tableViewFeeds.setContentOffset(CGPoint(x: 0, y: CGFLOAT_MAX), animated: true)
Amit Verma
fuente
2

Si tiene que cargar los datos de forma asincrónica antes de desplazarse hacia abajo, esta es la posible solución:

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
fuente
2

Función en swift 3 desplazarse hacia abajo

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

deberías agregar

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

o no se desplazará hasta el fondo.


fuente
2

Use este código simple para desplazarse por la tablaVer abajo

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}
Bibin Joseph
fuente
1
Esto es básicamente lo que el OP dijo que ya intentó. Esta respuesta no aborda la pregunta del OP sobre cómo hacer que funcione correctamente en viewWillAppear.
jk7
2

Gracias Jacob por la respuesta. realmente útil si alguien interesante con la versión 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
fuente
1

En iOS esto funcionó bien para mí

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

Necesita ser llamado desde viewDidLayoutSubviews

Josip B.
fuente
1

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

leetvin
fuente
1

La respuesta aceptada no funcionó con mi tabla (miles de filas, carga dinámica) pero el siguiente código funciona:

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

No es necesario desplazarse, puede hacerlo utilizando este código:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
Anuj Kumar Rai
fuente
1

Si está configurando el marco para la vista de tabla mediante programación, asegúrese de configurar el marco correctamente.

Narasimha Nallamsetty
fuente
0

En Swift, solo necesitas

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

para que se desplace automáticamente al botón

Incluso Cheng
fuente
0

En swift 3.0 Si desea ir a cualquier Celda particular de vista de tabla Cambie el valor del índice de celda como cambiar el valor "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
fuente
0

Creo que las soluciones antiguas no funcionan con swift3.

Si conoce filas de números en la tabla, puede usar:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
ymutlu
fuente