Como saber quando UITableView rolou para baixo no iPhone

100

Gostaria de saber quando UITableViewfoi rolar para baixo a fim de carregar e mostrar mais conteúdo, algo como um delegado ou outra coisa para informar o controlador quando a tabela rolar para baixo.

Alguém sabe disso, por favor me ajude, desde já agradeço!

Filho nguyen
fonte

Respostas:

18

A melhor maneira é testar um ponto na parte inferior da tela e usar esta chamada de método sempre que o usuário rolar ( scrollViewDidScroll):

- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point

Teste um ponto próximo à parte inferior da tela e, em seguida, usando o indexPath que ele retorna, verifique se esse indexPath é a última linha e, se for, adicione linhas.

Ajay
fonte
Infelizmente, meu aplicativo atualizará os dados em tempo real para as linhas existentes nesta tabela, então, desta forma, pode obter mais conteúdo quando a tabela não estiver rolando.
Son Nguyen
247

no delegado tableview faça algo assim

ObjC:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;
    // NSLog(@"offset: %f", offset.y);   
    // NSLog(@"content.height: %f", size.height);   
    // NSLog(@"bounds.height: %f", bounds.size.height);   
    // NSLog(@"inset.top: %f", inset.top);   
    // NSLog(@"inset.bottom: %f", inset.bottom);   
    // NSLog(@"pos: %f of %f", y, h);

    float reload_distance = 10;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}

Rápido:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let inset = scrollView.contentInset
    let y = offset.y + bounds.size.height - inset.bottom
    let h = size.height
    let reload_distance:CGFloat = 10.0
    if y > (h + reload_distance) {
        print("load more rows")
    }
}
neoneye
fonte
Boa! Basta usar: y> = h + reload_distance, que é realmente uma preocupação apenas quando reload_distance = 0 (por exemplo, para saltos NÃO).
Chris Prince de
4
Este código em "scrollViewDidScroll" é executado toda vez que o usuário move o dedo para cima / para baixo na tela. É melhor movê-lo para "scrollViewDidEndDecelerating". Desta forma, ele será executado uma vez quando o usuário parar de mover o dedo.
Misha
hmm .. este código ainda funciona? não obtendo o fundo do UITableView para mim. Eu gostaria de saber o raciocínio por trás de todo o código acima, então talvez eu possa corrigir
FlowUI. SimpleUITesting.com
offset: 237,000000 content.height: 855,000000 bounds.height: 667,000000 inset.top: 64,000000 inset.bottom: 49,000000 pos: 855,000000 de 855,000000 se está ficando falso
Abhishek Thapliyal
62

Neoneyes modificados respondem um pouco.

Esta resposta é direcionada àqueles que desejam que o evento seja acionado apenas uma vez a cada liberação do dedo .

Adequado ao carregar mais conteúdo de algum provedor de conteúdo (serviço da web, dados básicos, etc.). Observe que essa abordagem não respeita o tempo de resposta de seu serviço da web.

- (void)scrollViewDidEndDragging:(UIScrollView *)aScrollView
                  willDecelerate:(BOOL)decelerate
{
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;

    float reload_distance = 50;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}
Globo ocular
fonte
Isso não funciona. Sempre que o usuário rola para baixo, o "carregar mais linhas" é chamado. Isso acontece mesmo quando o usuário já rolou para o fundo e está esperando a API retornar os dados.
Dean
2
Dean, a questão era saber quando a exibição da mesa rolou para o fundo. Se você está obtendo dados de uma API ou não, é irrelevante, não é?
Eyeball
Mas não há mensagem mostrando load more .. para que o usuário saiba que há mais resultados a serem mostrados.
iPhone 7 de
Boa resposta no meu caso está funcionando se: float reload_distance = 10; if (y> (h - reload_distance)) {NSLog (@ "carregar mais linhas"); } porque y = 565 e h também é 565
Abhishek Thapliyal
1
Tentei as respostas de Eyeball e @neoneye. Acredite ou não, 'scrollViewDidScroll' chamado várias vezes em comparação com 'scrollViewDidEndDragging'. Portanto, foi eficiente usar 'scrollViewDidEndDragging' porque tenho uma chamada de API nele.
Vinod Supnekar de
39

adicione este método no UITableViewDelegate:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    CGFloat height = scrollView.frame.size.height;

    CGFloat contentYoffset = scrollView.contentOffset.y;

    CGFloat distanceFromBottom = scrollView.contentSize.height - contentYoffset;

    if(distanceFromBottom < height) 
    {
        NSLog(@"end of the table");
    }
}
8suhas
fonte
3
Eu gosto dessa solução, exceto por um pequeno detalhe. if (distanceFromBottom <= height)
Mark McCorkle
Isso me ajudou a solucionar meu problema, obrigado !! uma barra de guias !! era uma barra de guia estúpida !!
Dmytro
Isso funciona como um encanto e também é simples, mas estou enfrentando um pequeno problema, como se estivesse executando duas ou três vezes na última linha. como fazê-lo executar apenas uma vez ao atingir a última linha do tableview?
R. Mohan
Muito obrigado!! Eu coloquei este código em scrollViewWillBeginDecelerating e ele executa apenas uma vez quando rolado para a última linha.
Manali
20

Nenhuma das respostas acima me ajudou, então eu vim com o seguinte:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{   
    NSArray *visibleRows = [self.tableView visibleCells];
    UITableViewCell *lastVisibleCell = [visibleRows lastObject];
    NSIndexPath *path = [self.tableView indexPathForCell:lastVisibleCell];
    if(path.section == lastSection && path.row == lastRow)
    {
        // Do something here
    }
}
Iftach Orr
fonte
Você pode explicar de onde vem "lastSection"?
ainesophaur
isto é para visualização de rolagem e não tabela
iosMentalist
19

Use – tableView:willDisplayCell:forRowAtIndexPath:( UITableViewDelegatemétodo)

Basta comparar o indexPathcom os itens em sua matriz de dados (ou qualquer fonte de dados que você usa para sua exibição de tabela) para descobrir se o último elemento está sendo exibido.

Documentos: http://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:willDisplayCell:forRowAtIndexPath :

Wolfgang Schreurs
fonte
Então, se você recarregar os dados de tableView ', você ficará preso em um loop infinito.
Dielson Sales de
14

UITableViewé uma subclasse de UIScrollViewe UITableViewDelegateestá em conformidade com UIScrollViewDelegate. Portanto, o delegado que você anexa à visualização da tabela obterá eventos como scrollViewDidScroll:, e você pode chamar métodos como contentOffsetna visualização da mesa para encontrar a posição de rolagem.

Anomia
fonte
Sim, é isso que estou procurando, ao implementar esse delegado e verificar se a posição de rolagem é UITableViewScrollPositionBottom, saberei quando a tabela for rolada para baixo, muito obrigado
Son Nguyen
8
NSLog(@"%f / %f",tableView.contentOffset.y, tableView.contentSize.height - tableView.frame.size.height);

if (tableView.contentOffset.y == tableView.contentSize.height - tableView.frame.size.height)
        [self doSomething];

Bom e simples

user1641587
fonte
8

em Swiftvocê pode fazer algo assim. A seguinte condição será verdadeira toda vez que você chegar ao final do tableview

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row+1 == postArray.count {
            println("came to last row")
        }
}
Jay Mayu
fonte
1
o problema é quando tableview tem apenas 3 células, por exemplo, println("came to last row")este código é executado!
Mc.Lover
@Mc.Lover, esse era o problema exato para mim, pois o uso da CPU estava indo para 92%. a resposta de neoneye funcionou para mim. Eu também adicionei a versão rápida para ele, se necessário.
Anuran Barman
7

Com base na resposta de @Jay Mayu, que considero uma das melhores soluções:

Objective-C

// UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

// Need to call the service & update the array
if(indexPath.row + 1 == self.sourceArray.count) {
    DLog(@"Displayed the last row!");
  }
}

Swift 2.x

// UITableViewDelegate
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if (indexPath.row + 1) == sourceArray.count {
        print("Displayed the last row!")
    }
}
footyapps 27
fonte
5

Eu geralmente uso isso para carregar mais dados, quando a última célula começa a exibir

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   if (indexPath.row ==  myDataArray.count-1) {
        NSLog(@"load more");
    }
}
Shaik Riyaz
fonte
1
isto está errado. Cada vez que você chamar reloadData, uma solicitação será feita. E às vezes você só precisa recarregar a mesa.
Patrick Bassut,
você queria dizer, se você está na última célula e depois recarrega os dados, isso vai acontecer?
Shaik Riyaz
5

Pegando novas respostas excelentes, mas rapidamente, e renomeando as variáveis.

Basicamente, sabemos que atingimos a parte inferior da visualização da tabela se yOffsetAtBottomestiver além da altura do conteúdo da tabela.

func isTableViewScrolledToBottom() -> Bool {
    let tableHeight = tableView.bounds.size.height
    let contentHeight = tableView.contentSize.height
    let insetHeight = tableView.contentInset.bottom

    let yOffset = tableView.contentOffset.y
    let yOffsetAtBottom = yOffset + tableHeight - insetHeight

    return yOffsetAtBottom > contentHeight
}
Samwize
fonte
5

Aqui está o código da versão 3.0 do swift.

   func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let offset = scrollView.contentOffset
        let bounds = scrollView.bounds
        let size = scrollView.contentSize
        let inset = scrollView.contentInset
        let y: Float = Float(offset.y) + Float(bounds.size.height) + Float(inset.bottom)
        let height: Float = Float(size.height)
        let distance: Float = 10

        if y > height + distance {
            // load more data
        }
    }
Morshed Alam
fonte
3

Minha solução é adicionar células antes que tableview desacelere no deslocamento estimado. É previsível pela velocidade.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)offset {

    NSLog(@"offset: %f", offset->y+scrollView.frame.size.height);
    NSLog(@"Scroll view content size: %f", scrollView.contentSize.height);

    if (offset->y+scrollView.frame.size.height > scrollView.contentSize.height - 300) {
        NSLog(@"Load new rows when reaches 300px before end of content");
        [[DataManager shared] fetchRecordsNextPage];
    }
}
abeto
fonte
3

Atualização para Swift 3

A resposta de Neoneye funcionou melhor para mim no Objetivo C, isso é o equivalente à resposta no Swift 3:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let offset: CGPoint = scrollView.contentOffset
    let bounds: CGRect = scrollView.bounds
    let size: CGSize = scrollView.contentSize
    let inset: UIEdgeInsets = scrollView.contentInset
    let y: CGFloat = offset.y + bounds.size.height - inset.bottom
    let h: CGFloat = size.height
//        print("offset: %f", offset.y)
//        print("content.height: %f", size.height)
//        print("bounds.height: %f", bounds.size.height)
//        print("inset.top: %f", inset.top)
//        print("inset.bottom: %f", inset.bottom)
//        print("position: %f of %f", y, h)

    let reloadDistance: CGFloat = 10

    if (y > h + reloadDistance) {
            print("load more rows")
    }
}
Ibrahim
fonte
2

Quero realizar alguma ação em qualquer 1 Tableviewcell completa.

Portanto, o código é o link:

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSArray* cells = self.tableView.visibleCells;
    NSIndexPath *indexPath = nil ;

    for (int aIntCount = 0; aIntCount < [cells count]; aIntCount++)
    {

        UITableViewCell *cell = [cells objectAtIndex:aIntCount];
CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.tableView.superview];

        if (CGRectContainsRect(self.tableView.frame, cellRect))
        {
            indexPath = [self.tableView indexPathForCell:cell];

    //  remain logic
        }
     }
}

Que isso seja útil para alguém.

Mayuri R Talaviya
fonte
0

A resposta de @neoneye funcionou para mim. Aqui está a versão do Swift 4

    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let insets = scrollView.contentInset
    let y = offset.y + bounds.size.height - insets.bottom
    let h = size.height
    let reloadDistance = CGFloat(10)
    if y > h + reloadDistance {
        //load more rows
    }
Anuran Barman
fonte