UITableView无限滚动

43

如何在UITableView中实现无限滚动呢?我知道如何使用UIScrollView实现它,苹果在WWDC的视频中演示过。我尝试在tableView:cellForRowAtIndexPath:方法中做以下操作:

if (indexPath.row == [self.newsFeedData_ count] - 1)
{
    [self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
    [self.tableView reloadData];
}

但是这种方法失败了。还有其他想法吗?


如果在调用[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];之前和之后使用NSLog输出self.newsFeedData_,输出结果是否相同?(可以先尝试仅输出[self.newsFeedData_ count],看看数组中记录的数量是否增加了?) - runmad
这是一个使用Swift实现的无限UITableView演示:https://github.com/i-schuetz/tableview_infinite - User
我已经在这里添加了一个答案,它非常简单地使用了UITableViewDelegate table​View(_:​will​Display:​for​Row​At:​)实例方法。 - lukkea
https://github.com/canopas/MarqueeScroll - SPatel
9个回答

56
如果你需要知道何时到达UITableView的底部,可以成为其代理(因为它是UIScrollView的子类),并使用-scrollViewDidScroll:代理方法来比较表格的内容高度和实际滚动位置。
- (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];
     }
}

你必须拥有一个唯一的 uniqueNewsFeedData_ 数组,并从该数组中添加对象,因为如果你从同一个数组中添加对象,那么数组大小会增加,如 x、2x、4x、8x。 - iDev
1
如果有人好奇,所显示的“someArbitraryNumber”实际上是我的屏幕高度。 - Hari Ganesan
9
我将tableView.frame.size.height作为“someArbitraryNumber”使用。 - Daniel Gomez Rico
@adit 抱歉回复晚了,但这个答案是否有效,而你的原始代码没有?你那边有任何错误吗(例如空列表)? - Blaszard
如果您的新数据获取需要一些时间,您可能希望将“someArbitraryNumber”设置得比屏幕高度大得多。 - BallpointBen
显示剩余4条评论

18

您可以使用以下方法支持无限滚动,包括在顶部使用下拉刷新和/或在底部使用旋转轮:

https://github.com/samvermette/SVPullToRefresh

SVPullToRefresh 处理 UITableView 到达底部时的逻辑。自动显示一个旋转图标并触发回调块。您可以在回调块中添加您的业务逻辑。

以下是一个示例:

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

这个项目可以通过CocoaPods添加到你的项目中,也可以直接编译进你的项目中。

17

这是我制作的无限滚动UITableView非常快速和完整的演示...

@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

1
[self.tableView reloadData] 可能会导致页面闪烁。我知道可以使用 [self.tableView beginUpdates] 和 [self.tableView endUpdates],但不确定如何正确使用它。 - trillions
1
在我的“addSomeMOreEntriesToTableView”方法中,您可以创建一个NSIndexPath数组,然后调用beginUpdates,接着调用insertrowAtIndexPaths:withRowAnimations:(传递indexPaths数组),最后调用endUpdates,而不是调用reloadData。 - Oliver Pearmain
谢谢!我明天早上会试一下,现在太累了~稍后更新 :) - trillions
你最好使用委托方法 - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath。这样你就可以避免使用 performSelector - Sam Clewlow

12

在'scrollViewDidScroll'方法中,'UITableView'与'UIScrollView'相同。

因此,很容易模拟无限滚动。

  1. 将数组加倍,使头部和尾部连接起来以模拟循环表

  2. 使用以下代码,在用户试图到达表格的开始或结束时切换到加倍表格的第一部分和第二部分。

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

}

顺便说一下,我在我的一个名为NT Time Table (Lite)的应用程序中使用了这个代码。如果你想预览一下,可以查看这个应用程序:https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8

如果你的表格有时可能太短,在上述方法的开始处,你可以添加一个if逻辑来在数据数量少于9时退出该方法。


你好,能否告诉我"8.0"和"((contentHeight * 6)/ 8.0)"的计算方式?这与tableview中的“行数”有关吗?请告诉我。 - Nishant B
不,不是这样的。它表示整个表格长度的6/8(而不仅是可见区域)。因此,当用户到达表格末尾的6/8位置时,他们会被带到数据的第一部分(因为我们将数据加倍),在那里数据相同。同样地,当向上滚动到表格的1/8处时,他们会进入数据相同的表格的第二部分。 - MIWMIB

5

对我来说,scrollViewDidEndDragging:scrollViewDidScroll: 更好用。

第二种方法会在滚动时发送每个位置,并导致如果你正在获取远程资源,将多次击中你的端点,这是不好的。

基于 @codafi 的解决方案的完整示例,包括 @danielgomezrico 关于如何计算 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];
    }
}

3

通常我会覆盖scrollViewDidEndDecelerating方法,并在其中放置请求更多数据的代码。
示例:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

    float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;

    if (endScrolling >= scrollView.contentSize.height){
        //put here your code

    }
}

最近我在GitHub上上传了一个UITableView的子类,它实现了无限滚动。
你可以在这里下载它:
https://github.com/alchimya/iOS-LazyTableView


1

我们可以在layoutSubviews中进行最优化的实现,而不是覆盖原有的方法。 以下是我实现的方式。您可以在这里了解更多实现细节。

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

        }
    }

}

}

0

其中一个简单的类,也提供了我所需的一切:

https://github.com/jakemarsh/JMStatefulTableViewController

你只需要继承JMStatefulTableViewController,然后重写其中的3个方法:

  • 一个在初始化时调用,用于获取初始数据
    • statefulTableViewControllerWillBeginInitialLoading
  • 一个在用户下拉刷新时调用
    • statefulTableViewControllerWillBeginLoadingFromPullToRefresh
  • 一个在调用无限滚动(下一页)时调用
    • statefulTableViewControllerWillBeginLoadingNextPage

这也可以通过Cocoapods使用。


0
scrollviewDidScroll会在你在tableview中移动行时调用
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    //check for the visible rows
    let indexpath = self.tableView.indexPathsForVisibleRows?.last
    //check if the visible row last is equal to the total number of counts
    if(indexpath?.last == self.listCount){
      //code for adding data to the tableview and reload the table view.
    }
}

请查看以下链接,了解有关indexPathForVisibleRows的更多详细信息https://developer.apple.com/documentation/uikit/uitableview/1614885-indexpathsforvisiblerows


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接