iOS: Implementando scroll infinito em tabelas

A implementação de rolagem infinita está presente em diversos aplicativos que utilizam dados armazenados em servidores na internet, fazendo com que os usuários busquem estas informações na medida que for preciso exibir mais resultados. O que vemos muitas vezes em sites hoje em dia é aquela paginação padrão que em cada página possui um determinado número de resultados, mas o que irei demonstrar neste post é um aplicativo que possui uma lista e assim que o final desta lista for atingido será carregado mais informações, incrementando as que já existiam e não substituindo por novas.

Nota: o foco deste post é apenas demonstrar o funcionamento da rolagem infinita, com a premissa de você possuir uma classe com o devido protocolo implementado para retornar sucesso ou erro na conexão com o seu servidor, já que cada um desenvolve essa parte de maneira diferente. De qualquer forma, no projeto final anexado no final deste post você pode baixar e verificar a forma que eu fiz. Vamos lá!

As propriedades principais são cinco dentro da nossa classe UITableViewController:

@property (nonatomic, retain) Connect *connect;
@property (nonatomic, retain) NSMutableArray *data;
@property (nonatomic, retain) UIActivityIndicatorView *loadingIndicator;
@property (nonatomic) int page;
@property (nonatomic) BOOL isLoading;
  • connect: essa é a classe que realiza a conexão, iremos iniciá-la com um delegate, um @selector de sucesso e outro de erro;
  • data: o array que recebe as informações para serem exibidas em nossa lista, nesse caso possui somente uma string;
  • loadingIndicator: o indicador animado que ficará no rodapé da lista;
  • page: o número da página atual;
  • isLoading: indica se há uma requisição em andamento, assim podemos saber se podemos prosseguir para a próxima página;

No método viewDidLoad iniciaremos todas estas propriedades:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Começamos na página 1
    self.page = 1;

    // Indica que está sendo realizado uma requisição
    self.isLoading = YES;

    // Criamos o indicador para incluir no rodapé da tabela e inicia a animação
    self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.frame.size.width, 30.0f)];
    [self.loadingIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
    [self.loadingIndicator startAnimating];

    // Inicia o array que irá conter as informações da tabela
    self.data = [NSMutableArray array];

    // Inicia a classe de conexão, indicando os métodos para sucesso e erro que iremos implementar abaixo
    self.connect = [[Connect alloc] initWithDelegate:self
                                           onSuccess:@selector(responseSuccess:)
                                             onError:@selector(responseError:)];

    // Faz a requisição passando como parâmetro a página desejada
    [self.connect requestPage:self.page];
}

Como indicado no momento em que iniciamos a propriedade que realiza a conexão, temos agora os métodos em caso de sucesso e erro:

- (void)responseSuccess:(id)result {
    // A próxima requisição será feita para a próxima página
    self.page++;

    // Indica que não há requisição atualmente e finaliza a animação do rodapé
    [self setIsLoading:NO];
    [self.loadingIndicator stopAnimating];

    // Estes dois blocos são ilustrativos, pois eu simplesmente vou preencher a tabela com uma string qualquer, sempre com 20 elementos cada página
    // Aqui você deverá receber o resultado de sua requisição, seja JSON ou XML e tratá-lo de acordo com sua implementação
    int count = [self.data count];

    for (int i = count; i < (count + 20); i++) {
        [self.data addObject:[NSString stringWithFormat:@"Item %d", i]];
    }

    // Atualiza os dados da tabela
    [self.tableView reloadData];
}

- (void)responseError:(NSString *)error {
    // Indica que não há requisição atualmente e finaliza a animação do rodapé
    [self setIsLoading:NO];
    [self.loadingIndicator stopAnimating];

    // Mostra no console o erro, que deve ser tratado em sua classe de conexão
    NSLog(@"%@", error);
}

O método scrollViewDidScroll:scrollView é disparado toda vez que rolamos a tabela, portanto, é aqui que saberemos quando atingirmos o rodapé para então buscar as próximas páginas:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // Obtém a posição da coordenada Y em relação ao scoll da tabela
    NSInteger currentOffset = scrollView.contentOffset.y;

    // Quanto Y valerá ao atingir o rodapé da tabela
    NSInteger maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    // Não fazer nada caso não tenha atingido o rodapé
    if (maximumOffset - currentOffset > 1.0)
        return;

    // Caso atinga o rodapé da página, verificar se não existe uma requisição sendo processada, assim podemos prosseguir
    if (!self.isLoading) {
        // Faz uma requisição passando a página
        [self.connect requestPage:self.page];

        // Indica que uma requisição está sendo realizada
        [self setIsLoading:YES];
        [self.loadingIndicator startAnimating];
    }
}

E por fim, os métodos que correspondem ao conteúdo da tabela, com detalhe para o último método, onde de fato é adicionado o indicador animado que criamos lá em cima no método viewDidLoad:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.data count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    [cell.textLabel setText:[self.data objectAtIndex:[indexPath row]]];

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 30.0f;
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    return self.loadingIndicator;
}

Agora você pode executar seu projeto e o resultado será uma lista de itens, onde no momento que você rolar e atingir o rodapé desta lista, um indicador será exibido e uma requisição será feita para carregar a próxima página.

No link abaixo você poderá fazer download e executar diretamente do seu Xcode. Usei meramente como exemplo requisições à página do site The Verge, alterando as páginas somente para obter uma resposta HTTP 200. Como expliquei no início, o foco deste post não é desenvolver a classe de conexão e nem a forma como você irá tratar os resultados, mas você poderá entender como é o funcionamento investigando a classe Connect.

GitHub InfiniteScroll

Dúvidas? Comentários? Usem a área de comentário abaixo como desejarem.

1 Comentário

  1. Erica Responder

    Cara, que ótimo post! Salvou minha vida!

Deixe uma resposta