UITableView défilement infini
Comment faire un défilement infini dans un UITableView
? Je sais comment le faire en utilisant un UIScrollView
, dans lequel apple a démontré dans l'une des vidéos de la WWDC. J'ai essayé de faire ce qui suit dans tableView:cellForRowAtIndexPath:
:
if (indexPath.row == [self.newsFeedData_ count] - 1)
{
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
Mais cela échoue. Une autre idée?
8 réponses
Si vous avez besoin de savoir quand vous appuyez sur le bas de UITableView, devenez délégué (car il s'agit d'une sous-classe de UIScrollView), et utilisez la méthode-scrollViewDidScroll: delegate pour comparer la hauteur du contenu de la table et sa position de défilement réelle.
EDIT (quelque chose comme ça):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat actualPosition = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height - (someArbitraryNumber);
if (actualPosition >= contentHeight) {
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
}
Vous pouvez prendre en charge le défilement infini avec pull pour rafraîchir en haut et / ou faire défiler continuellement en bas avec une roue de spinner en utilisant:
Https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh
gère la logique lorsque UITableView
atteint le fond. Un spinner est affiché automatiquement et un bloc de rappel est déclenché. Vous ajoutez votre logique métier au bloc de rappel.
Voici un exemple:
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
Ce projet peut être ajouté à votre projet en utilisant CocoaPods {[7] } ou directement compilé dans votre projet.
Voici une démo très rapide et complète d'un UITableView à défilement infini que j'ai mis en place...
@interface InfiniteScrollViewController ()
@property (nonatomic) NSMutableArray *tableViewData;
@property (nonatomic) BOOL loadingMoreTableViewData;
@end
@implementation InfiniteScrollViewController
- (void)viewDidLoad {
self.tableViewData = [[NSMutableArray alloc] init];
[self addSomeMoreEntriesToTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableViewData.count + 1;
}
- (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];
}
if (indexPath.row < self.tableViewData.count) {
cell.textLabel.text = [self.tableViewData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = @"Loading more data...";
// User has scrolled to the bottom of the list of available data so simulate loading some more if we aren't already
if (!self.loadingMoreTableViewData) {
self.loadingMoreTableViewData = YES;
[self performSelector:@selector(addSomeMoreEntriesToTableView) withObject:nil afterDelay:5.0f];
}
}
return cell;
}
- (void)addSomeMoreEntriesToTableView {
int loopTill = self.tableViewData.count + 20;
while (self.tableViewData.count < loopTill) {
[self.tableViewData addObject:[NSString stringWithFormat:@"%i", self.tableViewData.count]];
};
self.loadingMoreTableViewData = NO;
[self.tableView reloadData];
}
@end
'UITableView' est identique à 'UIScrollView' dans la méthode' scrollViewDidScroll'.
Donc, il est facile d'émuler le défilement infini.
-
Doublez le tableau pour que head et tail soient réunis pour émuler la table circulaire
Utilisez mon code suivant pour faire basculer l'utilisateur entre la 1ère partie de la table doublée et la 2ème partie de la table doublée quand ils ont tendance à atteindre le début ou la fin de la table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S.-j'ai utilisé ce code sur une de mes applications appelée NT Time Table (Lite). Si vous voulez l'aperçu, vous pouvez vérifier l'application: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
Si votre table peut parfois être trop courte, au début de la méthode ci-dessus, vous pouvez ajouter une logique if pour quitter la méthode lorsque le nombre de données est par exemple inférieur à 9.
Pour moi a mieux fonctionné scrollViewDidEndDragging: que scrollViewDidScroll:.
La deuxième approche vous enverra chaque position pendant le défilement et la cause, si vous récupérez des ressources distantes, vous atteindrez votre point de terminaison plusieurs fois, ce qui n'est pas bon.
Exemple Complet basé sur @codafi solution avec les commentaires de @danielgomezrico sur la façon de calculer contentHeight:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
CGFloat actualPosition = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height - (self.tableView.frame.size.height);
if (actualPosition >= contentHeight) {
// fetch resources
[self.tableView reloadData];
}
}
Généralement, je remplace scrollViewDidEndDecelerating
et à l'intérieur je mets mon code pour demander plus de données.
Exemple:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height){
//put here your code
}
}
Récemment, j'ai téléchargé sur GitHub une sous-classe de UITableView, qui implémente le défilement infini.
Vous pouvez le télécharger ici:
https://github.com/alchimya/iOS-LazyTableView
Plutôt que de surcharger, nous pouvons le faire de manière optimale dans layoutSubviews. Voici comment je me suis mis en œuvre. Vous pouvez en savoir plus sur l'implémentation ici
- (void)layoutSubviews{
[super layoutSubviews];
if(self.delegateForViews){
CGPoint contentOffset = self.contentOffset;
if([self.delegateForViews noOfViews]>numOfReusableViews){
NSUInteger centerIndex=visibleViews.count/2;
NSUInteger noOfViews=[self.delegateForViews noOfViews];
UIView *centerView=[visibleViews objectAtIndex:centerIndex];
CGPoint centerViewOrigin=centerView.frame.origin;
CGSize centerViewSize=centerView.frame.size;
CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
if(offsetDifferenceAbs>=centerViewSize.width){
if(offsetDifference<0){
currentPosition--;
}else{
currentPosition++;
}
self.contentOffset=centerViewOrigin;
currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
[self.delegateForViews clearView:centerView];
[self.delegateForViews setupView:centerView forPosition:currentPosition];
for (int i=centerIndex-1; i>=0; i--) {
UIView* prevView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:prevView];
[self.delegateForViews setupView:prevView forPosition:
[self getPosition:currentPosition-1 noOfViews:noOfViews]];
}
for (int i=centerIndex+1; i<visibleViews.count; i++) {
UIView* nextView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:nextView];
[self.delegateForViews setupView:nextView forPosition:
[self getPosition:currentPosition+1 noOfViews:noOfViews]];
}
}
}
}
}
L'un des plus simples et qui m'a offert Tout ce dont j'ai besoin est cette classe:
Https://github.com/jakemarsh/JMStatefulTableViewController
Vous avez juste besoin de sous-classe JMStatefulTableViewController et il a 3 méthodes que vous devez écraser:
- celui qui est appelé init, pour obtenir les données initiales
- statefulTableViewControllerWillBegininitialloading
- Un lorsque l'utilisateur tire vers rafraîchir
- statefulTableViewControllerWillBeginloadingfrompulltorefresh
- Un quand est appelé pour le défilement infini (page suivante)
- statefulTableViewControllerWillBeginloadingnextpage
Cela peut également être utilisé à partir de Cocoapods.