【问题标题】:UITableView infinite scrollingUITableView 无限滚动
【发布时间】:2012-05-11 08:36:39
【问题描述】:

如何在UITableView 中无限滚动?我知道如何使用UIScrollView 来做到这一点,Apple 在其中一个 WWDC 的视频中进行了演示。我尝试在tableView:cellForRowAtIndexPath: 中执行以下操作:

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

但这失败了。还有什么想法吗?

【问题讨论】:

标签: iphone objective-c ios ipad


【解决方案1】:

如果你需要知道什么时候到达了 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];
     }
}

【讨论】:

  • hmm...你的数组可能是空的吗?
  • 您必须拥有一个 uniqueNewsFeedData_ 并从该数组添加对象,因为如果您从同一数组添加对象,则数组大小会增加,例如 x、2x、4x、8x。
  • 如果有人好奇的话,显示的someArbitraryNumber 原来是我的屏幕高度
  • 这非常适合滚动到底部,但是顶部呢?
  • 我用someArbitraryNumbertableView.frame.size.height
【解决方案2】:

您可以使用以下方式支持无限滚动,通过拉动在顶部刷新和/或在底部使用旋转轮连续滚动:

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将此项目添加到您的项目中,也可以直接编译到您的项目中。

【讨论】:

    【解决方案3】:

    这是我放在一起的无限滚动 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
    

    【讨论】:

    • [self.tableView reloadData] 会导致页面闪烁..我知道我可以使用 [self.tableView beginUpdates] 和 [self.tableView endUpdates] 但不知道如何正确使用它..跨度>
    • 是的,在我的“addSomeMOreEntriesToTableView”方法中,您将创建一个 NSIndexPath 数组,而不是调用 reloadData,然后调用 beginUpdates,然后是 insertrowAtIndexPaths:withRowAnimations:(传递 indexPaths 数组),最后是 endUpdates。
    • 谢谢!早上去试试,太累了~会更新的:)
    • 您最好使用委托方法- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath。这样你就可以避免用户performSelector
    【解决方案4】:

    'UITableView' 与 'scrollViewDidScroll' 方法中的 '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)”的计算方法?它与表格视图中的“行数”有关吗?请告诉我。
    • 不,不是。这意味着表格全长的 6/8(不仅仅是可见区域)。因此,当用户到达表的末尾(6/8)时,他们会被带到数据相同的第一部分(因为我们将数据翻了一番)。同样,当向上滚动到表格的 1/8 时,它们会被带到数据相同的表格的第二部分。
    【解决方案5】:

    对我来说,scrollViewDidEndDragging:scrollViewDidScroll: 效果更好。

    第二种方法会在滚动过程中向您发送每个位置,因为如果您正在获取远程资源,您将多次访问您的端点,这并不好。

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

    【讨论】:

      【解决方案6】:

      通常我会覆盖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

      【讨论】:

        【解决方案7】:

        我们可以在 layoutSubviews 中最佳地做到这一点,而不是覆盖。 这是我如何实现它的。您可以了解更多关于实现here

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

        【讨论】:

          【解决方案8】:

          其中一个简单的并且为我提供了我所需要的一切就是这个类:

          https://github.com/jakemarsh/JMStatefulTableViewController

          你只需要继承 JMStatefulTableViewController 并且它有 3 个你需要覆盖的方法:

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

          这也可以从Cocoapods 使用。

          【讨论】:

            【解决方案9】:

            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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-12-12
              • 1970-01-01
              • 2012-09-30
              • 2013-06-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多