【问题标题】:UITableView load more when scrolling to bottom like Facebook applicationUITableView 在像 Facebook 应用程序一样滚动到底部时加载更多
【发布时间】:2013-12-14 16:32:05
【问题描述】:

我正在开发一个使用 SQLite 的应用程序。我想使用分页机制显示用户列表(UITableView)。谁能告诉我当用户滚动到列表末尾时如何在我的列表中加载更多数据(比如在 Facebook 应用程序的主页上)?

【问题讨论】:

    标签: ios iphone uitableview pagination


    【解决方案1】:

    您可以通过在cellForRowAtIndexPath: 方法中添加对您所在位置的检查来做到这一点。此方法易于理解和实现:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // Classic start method
        static NSString *cellIdentifier = @"MyCell";
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
        if (!cell)
        {
            cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MainMenuCellIdentifier];
        }
    
        MyData *data = [self.dataArray objectAtIndex:indexPath.row];
        // Do your cell customisation
        // cell.titleLabel.text = data.title;
    
        BOOL lastItemReached = [data isEqual:[[self.dataArray] lastObject]]; 
        if (!lastItemReached && indexPath.row == [self.dataArray count] - 1)
        {
            [self launchReload];
        }
    }
    

    编辑:添加了对最后一项的检查以防止递归调用。您必须实现定义是否已到达最后一项的方法。

    EDIT2:解释 lastItemReached

    【讨论】:

    • 如果用户上下滚动怎么办,所以 cellForRowAtIndexPath 被多次调用!??
    • 第一次滚动到底部时,他的列表将被重新加载。每次他触底时,都会收集新的数据块。如果必须应用任何特定处理,则由 launchReload 方法负责处理(例如,一次只能执行一个异步重新加载操作)
    • 我必须添加一个标志以防止在最后一项被击中时出现递归问题:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
    • self.launchReload 方法是什么?
    • @shinyuX 对我不起作用,“如果”总是错误的...但是如果 (lastItemReached && indexPath.row == [self.dataArray count] - 1) 为真,为什么?
    【解决方案2】:

    斯威夫特

    方法一:滚动到底部

    这是Pedro Romão's answer 的 Swift 版本。当用户停止滚动时,它会检查它是否已到达底部。

    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
        // UITableView only moves in one direction, y axis
        let currentOffset = scrollView.contentOffset.y
        let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
    
        // Change 10.0 to adjust the distance from bottom
        if maximumOffset - currentOffset <= 10.0 {
            self.loadMore()
        }
    }
    

    方法2:到达最后一行

    这里是shinyuX's answer 的 Swift 版本。它检查用户是否已到达最后一行。

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        // set up cell
        // ...
    
        // Check if the last row number is the same as the last current data element
        if indexPath.row == self.dataArray.count - 1 {
            self.loadMore()
        }
    
    }
    

    loadMore() 方法示例

    我设置了这三个类变量来获取批量数据。

    // number of items to be fetched each time (i.e., database LIMIT)
    let itemsPerBatch = 50
    
    // Where to start fetching items (database OFFSET)
    var offset = 0
    
    // a flag for when all database items have already been loaded
    var reachedEndOfItems = false
    

    这是将更多项目从数据库加载到表视图中的功能。

    func loadMore() {
    
        // don't bother doing another db query if already have everything
        guard !self.reachedEndOfItems else {
            return
        }
    
        // query the db on a background thread
        DispatchQueue.global(qos: .background).async {
    
            // determine the range of data items to fetch
            var thisBatchOfItems: [MyObjects]?
            let start = self.offset
            let end = self.offset + self.itemsPerBatch
    
            // query the database
            do {
                // SQLite.swift wrapper
                thisBatchOfItems = try MyDataHelper.findRange(start..<end)
            } catch _ {
                print("query failed")
            }
    
            // update UITableView with new batch of items on main thread after query finishes
            DispatchQueue.main.async {
    
                if let newItems = thisBatchOfItems {
    
                    // append the new items to the data source for the table view
                    self.myObjectArray.appendContentsOf(newItems)
    
                    // reload the table view
                    self.tableView.reloadData()
    
                    // check if this was the last of the data
                    if newItems.count < self.itemsPerBatch {
                        self.reachedEndOfItems = true
                        print("reached end of data. Batch count: \(newItems.count)")
                    }
    
                    // reset the offset for the next data query
                    self.offset += self.itemsPerBatch
                }
    
            }
        }
    }
    

    【讨论】:

    • 我使用了方法 1,因为我想拉取更多。它工作得很好。谢谢你们!
    【解决方案3】:

    最好使用willDisplayCell 方法来检查是否将加载哪个单元格。 一旦我们得到当前的indexPath.row 是最后一个,我们就可以加载更多的单元格。 这将在向下滚动时加载更多单元格。

     - (void)tableView:(UITableView *)tableView 
           willDisplayCell:(UITableViewCell *)cell    
           forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // check if indexPath.row is last row
        // Perform operation to load new Cell's.
    }
    

    【讨论】:

    • 不是更好,因为 reloadData 会再次调用此方法对吗?
    • 是的,这适用于部分,indexPath 将为您提供行和部分。
    【解决方案4】:

    详情

    • Swift 5.1,Xcode 11.2.1

    解决方案

    使用 UIScrollView / UICollectionView / UITableView

    import UIKit
    
    class LoadMoreActivityIndicator {
    
        private let spacingFromLastCell: CGFloat
        private let spacingFromLastCellWhenLoadMoreActionStart: CGFloat
        private weak var activityIndicatorView: UIActivityIndicatorView?
        private weak var scrollView: UIScrollView?
    
        private var defaultY: CGFloat {
            guard let height = scrollView?.contentSize.height else { return 0.0 }
            return height + spacingFromLastCell
        }
    
        deinit { activityIndicatorView?.removeFromSuperview() }
    
        init (scrollView: UIScrollView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) {
            self.scrollView = scrollView
            self.spacingFromLastCell = spacingFromLastCell
            self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart
            let size:CGFloat = 40
            let frame = CGRect(x: (scrollView.frame.width-size)/2, y: scrollView.contentSize.height + spacingFromLastCell, width: size, height: size)
            let activityIndicatorView = UIActivityIndicatorView(frame: frame)
            if #available(iOS 13.0, *)
            {
                activityIndicatorView.color = .label
            }
            else
            {
                activityIndicatorView.color = .black
            }
            activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
            activityIndicatorView.hidesWhenStopped = true
            scrollView.addSubview(activityIndicatorView)
            self.activityIndicatorView = activityIndicatorView
        }
    
        private var isHidden: Bool {
            guard let scrollView = scrollView else { return true }
            return scrollView.contentSize.height < scrollView.frame.size.height
        }
    
        func start(closure: (() -> Void)?) {
            guard let scrollView = scrollView, let activityIndicatorView = activityIndicatorView else { return }
            let offsetY = scrollView.contentOffset.y
            activityIndicatorView.isHidden = isHidden
            if !isHidden && offsetY >= 0 {
                let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
                let offsetDelta = offsetY - contentDelta
                
                let newY = defaultY-offsetDelta
                if newY < scrollView.frame.height {
                    activityIndicatorView.frame.origin.y = newY
                } else {
                    if activityIndicatorView.frame.origin.y != defaultY {
                        activityIndicatorView.frame.origin.y = defaultY
                    }
                }
    
                if !activityIndicatorView.isAnimating {
                    if offsetY > contentDelta && offsetDelta >= spacingFromLastCellWhenLoadMoreActionStart && !activityIndicatorView.isAnimating {
                        activityIndicatorView.startAnimating()
                        closure?()
                    }
                }
    
                if scrollView.isDecelerating {
                    if activityIndicatorView.isAnimating && scrollView.contentInset.bottom == 0 {
                        UIView.animate(withDuration: 0.3) { [weak self] in
                            if let bottom = self?.spacingFromLastCellWhenLoadMoreActionStart {
                                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
                            }
                        }
                    }
                }
            }
        }
    
        func stop(completion: (() -> Void)? = nil) {
            guard let scrollView = scrollView , let activityIndicatorView = activityIndicatorView else { return }
            let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
            let offsetDelta = scrollView.contentOffset.y - contentDelta
            if offsetDelta >= 0 {
                UIView.animate(withDuration: 0.3, animations: {
                    scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
                }) { _ in completion?() }
            } else {
                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
                completion?()
            }
            activityIndicatorView.stopAnimating()
        }
    }
    

    用法

    初始化

    activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
    

    处理

    extension ViewController: UITableViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            activityIndicator.start {
                DispatchQueue.global(qos: .utility).async {
                    sleep(3)
                    DispatchQueue.main.async { [weak self] in
                        self?.activityIndicator.stop()
                    }
                }
            }
        }
    }
    

    完整样本

    不要忘记粘贴解决方案代码。

    import UIKit
    
    class ViewController: UIViewController {
        
        fileprivate var activityIndicator: LoadMoreActivityIndicator!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            let tableView = UITableView(frame: view.frame)
            view.addSubview(tableView)
            tableView.translatesAutoresizingMaskIntoConstraints = false
            tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
            
            tableView.dataSource = self
            tableView.delegate = self
            tableView.tableFooterView = UIView()
            activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
        }
    }
    
    extension ViewController: UITableViewDataSource {
        
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 30
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell()
            cell.textLabel?.text = "\(indexPath)"
            return cell
        }
    }
    
    extension ViewController: UITableViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            activityIndicator.start {
                DispatchQueue.global(qos: .utility).async {
                    for i in 0..<3 {
                        print("!!!!!!!!! \(i)")
                        sleep(1)
                    }
                    DispatchQueue.main.async { [weak self] in
                        self?.activityIndicator.stop()
                    }
                }
            }
        }
    }
    

    结果

    【讨论】:

    • 完美运行。但是我的表格视图中有一个标题,在拖动以加载更多内容后,标题将位于导航栏下方.. loadMoreActionFinshed 中的 UIEdgeInsetsMake 应设置为 (62, 0, 0, 0) 考虑到 66 = navbar.height + 22
    • 垂直滚动时它应该在 CollectionView 中工作。
    • 难以置信...酷!
    • 这个的任何objective-c版本?
    • @VasilyBodnarchuk 没问题,我会做的,在这里分享给其他人
    【解决方案5】:
    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
        NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
        NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
        if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) {
            // This is the last cell
            [self loadMore];
        }
    }
    

    如果您使用 Core Data 和 NSFetchedResultsController,那么 loadMore 可能如下所示:

    // Load more
    - (void)loadMore {
        [self.fetchedResultsController.fetchRequest setFetchLimit:newFetchLimit];
        [NSFetchedResultsController deleteCacheWithName:@"cache name"];
        NSError *error;
        if (![self.fetchedResultsController performFetch:&error]) {
            // Update to handle the error appropriately.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        }
    
        [self.tableView reloadData];
    }
    

    【讨论】:

    • 我正在尝试实现这一点,但我使用的是结果数组而不是 sqlite,想知道如何在我拥有的当前 NSMutableArray 中添加更多内容,然后重新加载数据,否则数据被覆盖...我试过这个 [names addObjectsFromArray: [responseObject valueForKeyPath:@"name"]];但它不起作用......这是我的问题的链接stackoverflow.com/questions/23446780/…
    • 每次获取新数据时重新获取数据有什么意义?如果 frc 配置正确,单次 fetch 就足够了,它会根据需要进行相应的更新。每次都获取它,假设frc的获取请求配置到主线程上下文将阻塞主线程,因为它撞击磁盘,当用户想要新数据时,这完全不利于用户体验。
    • 前半部分对我很有帮助,谢谢。 (不使用 FetchedResultsVC)
    • @MANIAK_dobrii 是正确的。 NSFetchedResultsController 的关键特性之一是它计算分页数据,以便在将其连接到 UITableView 时免费获得虚拟滚动。仅当您实际上用更多数据填充 CoreData 存储时才需要实现这样的 loadMore 函数,在这种情况下,如果您的 NSFetchedResultsController 配置正确,则无需执行另一个 performFetch。
    • 与其他答案相同的问题。 reloadData 导致这种情况发生多次。
    【解决方案6】:

    详情

    • Swift 5.1,Xcode 11.3.1

    解决方案

    Loadmore 的遗传 UITableView 扩展。

    在你的新文件中添加这个 UITableView + Extension

    extension UITableView {
    
    func indicatorView() -> UIActivityIndicatorView{
        var activityIndicatorView = UIActivityIndicatorView()
        if self.tableFooterView == nil {
            let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 80)
            activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
            activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
            
            if #available(iOS 13.0, *) {
                activityIndicatorView.style = .large
            } else {
                // Fallback on earlier versions
                activityIndicatorView.style = .whiteLarge
            }
            
            activityIndicatorView.color = .systemPink
            activityIndicatorView.hidesWhenStopped = true
    
            self.tableFooterView = activityIndicatorView
            return activityIndicatorView
        }
        else {
            return activityIndicatorView
        }
    }
    
    func addLoading(_ indexPath:IndexPath, closure: @escaping (() -> Void)){
        indicatorView().startAnimating()
        if let lastVisibleIndexPath = self.indexPathsForVisibleRows?.last {
            if indexPath == lastVisibleIndexPath && indexPath.row == self.numberOfRows(inSection: 0) - 1 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    closure()
                }
            }
        }
    }
    
    func stopLoading() {
        if self.tableFooterView != nil {
            self.indicatorView().stopAnimating()
            self.tableFooterView = nil
        }
        else {
            self.tableFooterView = nil
        }
    } 
    }
    

    现在,只需在 UITableViewDelegate Method willDisplay Cell 中添加以下代码行并确保 tableView.delegate = self

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // need to pass your indexpath then it showing your indicator at bottom 
        tableView.addLoading(indexPath) {
            // add your code here
            // append Your array and reload your tableview
            tableView.stopLoading() // stop your indicator
        }
    }
    

    结果

    就是这样..希望这有帮助。谢谢你

    【讨论】:

    • 需要考虑的事项。只需在 stoploading 函数中添加 'tableFooterView = nil' ,否则指示器旋转不会停止动画。 activityIndi​​cator 'hidesWhenStopped' 中还有一个属性,因此您无需手动设置隐藏的真/假指标。但总的来说它看起来很棒:)
    • 感谢您的建议,我将检查一次并编辑此答案:-)
    • 谢谢兄弟。这个答案比互联网上任何其他解决方案都要好。
    • 只是几件事。 1)每次要显示单元格时都会调用“addLoading”。在检查 lastVisibleIndexPath 等之前,indicatorView 不应启动动画。 2)“addLoading”使用indicatorView,每次用UIActivityIndi​​catorView()创建。如果 tableViewFooter 为 nil,则您已经从那里创建了它。如果 tableViewFooter 不是 nil,你可以检查它是否真的是 UIActivityIndi​​catorView,而不是创建一个新的。
    【解决方案7】:

    我已经实现了我在 stackoverflow 中找到的一种解决方案,它运行良好,但我认为 shinyuX 的解决方案非常容易实现,并且对于我的建议运行良好。 如果有人想要不同的解决方案,可以使用下面的这个。

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    
       // UITableView only moves in one direction, y axis
        CGFloat currentOffset = scrollView.contentOffset.y;
        CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
    
        //NSInteger result = maximumOffset - currentOffset;
    
        // Change 10.0 to adjust the distance from bottom
        if (maximumOffset - currentOffset <= 10.0) {
            [self loadOneMorePage];
            //[self methodThatAddsDataAndReloadsTableView];
        }
    }
    

    【讨论】:

    • 我认为视图呈现有不同的场景,在我的情况下你的解决方案有效,我需要这样的东西
    • 如果用户用力一甩,即1.5屏幕高,可以到达底部而不触发刷新。
    • 但它会将列表滚动到顶部
    【解决方案8】:

    在您的查询中使用限制和偏移量,并用该内容填充您的表格视图。当用户向下滚动时,加载下一个偏移量。

    在您的UITableViewDelegate 中实现tableView:willDisplayCell:forRowAtIndexPath: 方法并检查它是否是最后一行

    【讨论】:

      【解决方案9】:

      以下链接将提供示例代码。 #Swift3

      用户需要拉起最后一个表格视图单元格,至少 2 个单元格的高度才能从服务器获取更多数据。

      您会发现 Process 单元格也显示加载过程,就像在最后一个单元格中一样。

      它在 Swift3 中

      https://github.com/yogendrabagoriya/YBTableViewPullData

      【讨论】:

        【解决方案10】:

        另一个使用选项(Swift 3 和 iOS 10+):

        class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
        
             var currentPage: Int = 1
             let pageSize: Int = 10 // num of items in one page
        
             override func viewDidLoad() {
                 super.viewDidLoad()
        
                 self.tableView.prefetchDataSource = self
             }
        
             func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
                 let upcomingRows = indexPaths.map { $0.row }
        
                 if let maxIndex = upcomingRows.max() {
        
                    let nextPage: Int = Int(ceil(Double(maxIndex) / Double(pageSize))) + 1
        
                    if nextPage > currentPage {
                         // Your function, which attempts to load respective page from the local database
                         loadLocalData(page: nextPage)
        
                         // Your function, which makes a network request to fetch the respective page of data from the network
                         startLoadingDataFromNetwork(page: nextPage) 
        
                         currentPage = nextPage
                     }
                 }
             }
         }
        

        对于相当小的页面(约 10 个项目),您可能需要手动为第 1 页和第 2 页添加数据,因为 nextPage 可能在大约 1-2 的某个位置,直到表格有几个项目可以很好地滚动。但它适用于所有后续页面。

        【讨论】:

        • 这仅适用于只读数据。不起作用如果您具有删除某些行并加载更多的功能,因为 pageSize 已在此处修复,并且即使在更新源后有更多数据也无法加载更多。
        【解决方案11】:
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        
            if (news.count == 0) {
                return 0;
            } else {
                return news.count +  1 ;
            }
        }
        
        - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            @try {
        
                uint position = (uint) (indexPath.row);
                NSUInteger row = [indexPath row];
                NSUInteger count = [news count];
        
                //show Load More
                if (row == count) {
                    UITableViewCell *cell = nil;
        
                    static NSString *LoadMoreId = @"LoadMore";
                    cell = [tableView dequeueReusableCellWithIdentifier:LoadMoreId];
                    if (cell == nil) {
                        cell = [[UITableViewCell alloc]
                                initWithStyle:UITableViewCellStyleDefault
                              reuseIdentifier:LoadMoreId];
                    }
                    if (!hasMoreLoad) {
                        cell.hidden = true;
                    } else {
        
                        cell.textLabel.text = @"Load more items...";
                        cell.textLabel.textColor = [UIColor blueColor];
                        cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
                        NSLog(@"Load more");
                        if (!isMoreLoaded) {
                            isMoreLoaded = true;
                            [self performSelector:@selector(loadMoreNews) withObject:nil afterDelay:0.1];
                        }
                    }
        
                    return cell;
        
                } else {
                    NewsRow *cell = nil;
        
                    NewsObject *newsObject = news[position];
                    static NSString *CellIdentifier = @"NewsRow";
                    cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        
                    if (cell == nil) {
                        // Load the top-level objects from the custom cell XIB.
                        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
                        // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
                        cell = topLevelObjects[0];
                        // Configure the cell...
        
                    }
        
                    cell.title.text = newsObject.title;             
                    return cell;
                }
        
            }
            @catch (NSException *exception) {
                NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]);
            }
            return nil;
        }
        

        这篇文章的解释很好。

        http://useyourloaf.com/blog/2010/10/02/dynamically-loading-new-rows-into-a-table.html

        很简单,您必须添加最后一行并将其隐藏,当表格行到达最后一行时,而不是显示该行并加载更多项目。

        【讨论】:

          【解决方案12】:

          你应该检查 ios UITableViewDataSourcePrefetching。

          class ViewController: UIViewController {
              @IBOutlet weak var mytableview: UITableView!
          
              override func viewDidLoad() {
                  super.viewDidLoad()
                  mytableview.prefetchDataSource = self
              }
          
           func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
                  print("prefetchdRowsAtIndexpath \(indexPaths)")
              }
          
              func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
                  print("cancelPrefetchingForRowsAtIndexpath \(indexPaths)")
              }
          
          
          }
          

          【讨论】:

            【解决方案13】:

            对于 Xcode 10.1、Swift 4.2

            This video 似乎是一个很棒的教程!

            启动/完成项目:https://github.com/RobCanton/Swift-Infinite-Scrolling-Example

            import UIKit
            
            class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
            
                var tableView:UITableView!
            
                var fetchingMore = false
                var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
            
                override func viewDidLoad() {
                    super.viewDidLoad()
                    // Do any additional setup after loading the view, typically from a nib.
                    initTableView()
                }
            
                func initTableView() {
                    tableView = UITableView(frame: view.bounds, style: .plain)
                    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
                    tableView.delegate = self
                    tableView.dataSource = self
            
                    view.addSubview(tableView)
                    tableView.translatesAutoresizingMaskIntoConstraints = false
            
                    let layoutGuide = view.safeAreaLayoutGuide
                    tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
                    tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
                    tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
                    tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
            
                    tableView.reloadData()
                }
                override func didReceiveMemoryWarning() {
                    super.didReceiveMemoryWarning()
                    // Dispose of any resources that can be recreated.
                }
            
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                    return items.count
                }
            
                func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                        let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
                        cell.textLabel?.text = "Item \(items[indexPath.row])"
                        return cell
                }
            
                func scrollViewDidScroll(_ scrollView: UIScrollView) {
                    let offsetY = scrollView.contentOffset.y
                    let contentHeight = scrollView.contentSize.height
            
                    if offsetY > contentHeight - scrollView.frame.height * 4 {
                        if !fetchingMore {
                            beginBatchFetch()
                        }
                    }
                }
            
                func beginBatchFetch() {
                    fetchingMore = true
                    print("Call API here..")
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.50, execute: {
                        print("Consider this as API response.")
                        let newItems = (self.items.count...self.items.count + 12).map { index in index }
                        self.items.append(contentsOf: newItems)
                        self.fetchingMore = false
                        self.tableView.reloadData()
                    })
                }
            }
            

            【讨论】:

              【解决方案14】:

              用于从 API 加载,它适用于我,Xcode10,swift 4.2

              1- 创建新的 Swift 文件并这样做:

              //
              //  apiTVCController.swift
              //  ApiTestingTableView
              //
              //  Created by Hooma7n on 4/7/19.
              //  Copyright © 2019 Hooma7n. All rights reserved.
              //
              
              import Foundation
              import Alamofire
              
              class apiget {
              
                  var tableData : [Datum] = []
                  var loadin : [Datum] = []
                  var testfortotal : Int?
              
              
                  func getfromapi(completionHandler : ((_ isSucess : Bool) -> Void)?) {
                      let url = "https://reqres.in/api/users?page=1"
                      Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
                          .responseJSON(completionHandler : { response in
                              switch response.result {
                              case .success(let data):
                                  guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                                  let decoder = JSONDecoder()
                                  guard let result = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                                  self.tableData = result.data ?? []
                                  self.testfortotal = result.total ?? 0
                                  completionHandler?(true)
              
                              //                    print(result)
                              case .failure(let error):
                                  print(error)
                              }
                          })
                  }
              
                  var pagecounter : Int = 2
              
              
                  func loadmore(completionHandler : ((_ isSucess : Bool) -> Void)?){
              
                      let url = "https://reqres.in/api/users?page=\(pagecounter)"
                      Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
                          .responseJSON(completionHandler : { response in
                              switch response.result {
                              case .success(let data):
                                  guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                                  let decoder = JSONDecoder()
                                  guard let myresult = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                                  self.loadin = myresult.data ?? []
                                  self.tableData.append(contentsOf: myresult.data ?? [])
                                  completionHandler?(true)
                                  print(self.pagecounter)
                                  self.pagecounter += 1
              
                              //                    print(myresult)
                              case .failure(let error):
                                  print(error)
                              }
                          })
              
                  }
              
              }
              
              extension apiget {
              
                  struct Welcome: Codable {
                      let page, perPage, total, totalPages: Int?
                      var data: [Datum]?
              
                      enum CodingKeys: String, CodingKey {
                          case page
                          case perPage = "per_page"
                          case total
                          case totalPages = "total_pages"
                          case data
                      }
                  }
              
                  struct Datum: Codable {
                      let id: Int?
                      let firstName, lastName: String?
                      let avatar: String?
              
                      enum CodingKeys: String, CodingKey {
                          case id
                          case firstName = "first_name"
                          case lastName = "last_name"
                          case avatar
                      }
                  }
              
              
              }
              

              2- 在您的 ViewController 文件(tableView 控制器)中:

              //
              //  apiTVC.swift
              //  ApiTestingTableView
              //
              //  Created by Hooma7n on 4/7/19.
              //  Copyright © 2019 Hooma7n. All rights reserved.
              //
              
              import UIKit
              import Alamofire
              
              class apiTVC: UITableViewController {
              
                  var datamodel = apiget()
              
                  override func viewDidLoad() {
                      super.viewDidLoad()
              
                      datamodel.getfromapi(completionHandler: {finish in
                          if finish {self.tableView.reloadData()
                          }
              
                      })
              
                  }
              
              
                  override func numberOfSections(in tableView: UITableView) -> Int {
                      return 1
                  }
              
                  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                      return datamodel.tableData.count
                  }
              
                  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
              
                      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! apiTableViewCell
                      cell.firstNameLabel.text = datamodel.tableData[indexPath.row].firstName
                      cell.lastNameLabel.text = datamodel.tableData[indexPath.row].lastName
                      cell.dateLabel.text = "\(datamodel.tableData[indexPath.row].id ?? 0)"
                      cell.profileImageView.loadImage(fromURL: datamodel.tableData[indexPath.row].avatar ?? "")
              
                      return cell
              
                  }
              
                  override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
                      let lastElement = datamodel.tableData.count - 1
                      let total = datamodel.testfortotal ?? 12
                      if indexPath.row == lastElement && datamodel.tableData.count < total{
              
                          datamodel.loadmore(completionHandler: {finish in
                              if finish {
              
                                  self.tableView.reloadData()
              
                              }})
                      }
                  }
              }
              

              如果在你的 viewController 中使用 tableView 设置 delegate,datasource self in viewDidLoad.

              【讨论】:

                【解决方案15】:

                只想分享这种方法:

                - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
                {
                    NSLog(@"%@", [[YourTableView indexPathsForVisibleRows] lastObject]);
                    [self estimatedTotalData];
                }
                
                - (void)estimatedTotalData
                {
                    long currentRow = ((NSIndexPath *)[[YourTableView indexPathsForVisibleRows] lastObject]).row;
                
                    long estimateDataCount = 25;
                
                    while (currentRow > estimateDataCount)
                    {
                        estimateDataCount+=25;
                    }
                
                    dataLimit = estimateDataCount;
                
                    if (dataLimit == currentRow+1)
                    {
                        dataLimit+=25;
                    }
                
                    NSLog(@"dataLimit :%ld", dataLimit);
                
                    [self requestForData];
                
                    // this answers the question..
                    //
                    if(YourDataSource.count-1 == currentRow)
                    {
                        NSLog(@"LAST ROW"); //loadMore data
                    }
                }
                

                NSLog(...); 输出类似于:

                <NSIndexPath: 0xc0000000002e0016> {length = 2, path = 0 - 92}
                dataLimit :100
                <NSIndexPath: 0xc000000000298016> {length = 2, path = 0 - 83}
                dataLimit :100
                <NSIndexPath: 0xc000000000278016> {length = 2, path = 0 - 79}
                dataLimit :100
                <NSIndexPath: 0xc000000000238016> {length = 2, path = 0 - 71}
                dataLimit :75
                <NSIndexPath: 0xc0000000001d8016> {length = 2, path = 0 - 59}
                dataLimit :75
                <NSIndexPath: 0xc0000000001c0016> {length = 2, path = 0 - 56}
                dataLimit :75
                <NSIndexPath: 0xc000000000138016> {length = 2, path = 0 - 39}
                dataLimit :50
                <NSIndexPath: 0xc000000000120016> {length = 2, path = 0 - 36}
                dataLimit :50
                <NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
                dataLimit :25
                <NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
                dataLimit :25
                

                这对于显示本地存储的数据很有用。 最初我将 dataLimit 声明为 25,这意味着 uitableview 将有 0-24(最初)。

                如果用户滚动到底部并且最后一个单元格可见,dataLimit 将添加 25...

                注意:这更像是一个 UITableView 数据分页,:)

                【讨论】:

                  【解决方案16】:
                  -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
                  
                  NSInteger sectionsAmount = [tableView numberOfSections];
                  NSInteger rowsAmount = [tableView numberOfRowsInSection:[indexPath section]];
                  if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) {
                      //get last row
                      if (!isSearchActive && !isFilterSearchActive) {
                          if (totalRecords % 8 == 0) {
                              int64_t delayInSeconds = 2.0;
                              dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
                              dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
                  
                  
                              [yourTableView beginUpdates];
                              [yourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
                              [yourTableView endUpdates];
                              });
                          }
                      }
                  }
                  }
                  

                  【讨论】:

                  • 在显示最后一行之后,插入行,即 beginUpdates..并使用一些延迟来避免崩溃。
                  【解决方案17】:

                  解决此问题的最佳方法是在表格底部添加单元格,该单元格将包含指示器。

                  在swift中你需要添加这个:

                  1. 创建 cellLoading 类型的新单元格,这将保存指标。看下面的代码
                  2. 查看行数并加1(这是用于加载单元格)。
                  3. 如果 idexPath.row == yourArray.count 则需要检查 rawAtIndex,然后返回加载单元格。

                  看看下面的代码:

                  import UIKit
                  
                  class LoadingCell: UITableViewCell {
                  
                  @IBOutlet weak var indicator: UIActivityIndicatorView!
                  
                  
                  }
                  

                  对于表格视图: numOfRows:

                  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                      return  yourArray.count + 1
                  }
                  

                  cellForRawAt 索引路径:

                  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                  
                      if indexPath.row == users.count  {
                          // need to change
                          let loading = Bundle.main.loadNibNamed("LoadingCell", owner: LoadingCell.self , options: nil)?.first as! LoadingCell
                          return loading
                  
                      }
                  
                      let yourCell = tableView.dequeueReusableCell(withIdentifier: "cellCustomizing", for: indexPath) as! UITableViewCell
                  
                      return yourCell
                  
                  }
                  

                  如果您注意到我的加载单元是从 nib 文件创建的。 This videos 会解释我做了什么。

                  【讨论】:

                    【解决方案18】:
                    let threshold = 100.0 // threshold from bottom of tableView
                    var isLoadingMore = false // flag
                    
                    
                    func scrollViewDidScroll(scrollView: UIScrollView) {
                        let contentOffset = scrollView.contentOffset.y
                        let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
                    
                        if !isLoadingMore && (maximumOffset - contentOffset <= threshold) {
                            // Get more data - API call
                            self.isLoadingMore = true
                    
                            // Update UI
                            dispatch_async(dispatch_get_main_queue()) {
                                tableView.reloadData()
                                self.isLoadingMore = false
                            }
                        }
                      }
                    

                    【讨论】:

                      【解决方案19】:

                      这是示例代码。

                      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                          let cell:ShowComplainCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! ShowComplainCell
                      
                          let item  = self.dataArray[indexPath.row] as! ComplainListItem;
                      
                          let indexPathArray = NSArray(array: tableView.indexPathsForVisibleRows!)
                          let vIndexPath = indexPathArray.lastObject as! NSIndexPath
                      
                          let lastItemReached = item.isEqual(self.dataArray.lastObject);
                      
                          if (lastItemReached && vIndexPath.row == (self.dataArray.count - 1))
                             {
                      
                              self.loadData()
                             }
                          
                      
                      
                          return cell
                          
                      }
                      

                      indexPathArray: 是可见的行。

                      vIndexPath:最后一个索引路径可见

                      加载数据

                       func loadData(){
                      
                             if(isReloadTable){
                             let HUD = MBProgressHUD.showAdded(to: self.view, animated: true)
                             let manager :AFHTTPSessionManager = AFHTTPSessionManager()
                           
                             
                                 var param = NSDictionary()
                                 param = [
                                     "category":cat_id,
                                     "smart_user_id": USERDEF.value(forKey: "user_id") as! String,
                                     "page":page,
                                     "phone":phone! as String
                                     
                                 ] as [String : Any] as NSDictionary
                                 print("param1 = \(param)")
                      
                                 manager.get("lists.php?", parameters: param, progress: nil, success: { (task:URLSessionDataTask, responseObject: Any) in
                                     
                           
                                             let adsArray =  dic["results"] as! NSArray;
                                             for item in adsArray {
                                                 let item  = ComplainListItem(dictionary: item as! NSDictionary )
                                                 self.dataArray.add(item)
                                             }
                                         
                                             self.view.addSubview(self.cityTableView)
                                             self.cityTableView.reloadData()
                                         
                                         if(adsArray.count==10){
                                             self.cityTableView.reloadData()
                                             self.isReloadTable = true
                                             self.page+=1
                                         }else if(adsArray.count<10){
                                             self.cityTableView.reloadData()
                                             self.isReloadTable = false
                                     }
                                     
                                     HUD.hide(animated:true)
                                     
                                 }) { (operation,error) -> Void in
                                     print("error = \(error)")
                                     HUD.hide(animated:true)
                                 }
                             }
                              
                         }
                      

                      检查您的 dataArray 计数,即 myadsarray 检查是否等于您的数据限制。然后如果dataArray count等于下一页,如果不等于小于10,则调用所有数据。

                      【讨论】:

                        猜你喜欢
                        • 2016-04-07
                        • 2015-10-25
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多