简体   繁体   English

UITableView 在像 Facebook 应用程序一样滚动到底部时加载更多

[英]UITableView load more when scrolling to bottom like Facebook application

I am developing an application that uses SQLite.我正在开发一个使用 SQLite 的应用程序。 I want to show a list of users (UITableView) using a paginating mechanism.我想使用分页机制显示用户列表(UITableView)。 Could any one please tell me how to load more data in my list when the user scrolls to the end of the list (like on home page on Facebook application)?谁能告诉我当用户滚动到列表末尾时如何在我的列表中加载更多数据(比如在 Facebook 应用程序的主页上)?

You can do that by adding a check on where you're at in the cellForRowAtIndexPath: method.您可以通过在cellForRowAtIndexPath:方法中添加对您所在位置的检查来做到这一点。 This method is easy to understand and to implement :此方法易于理解和实施:

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

EDIT : added a check on last item to prevent recursion calls.编辑:添加了对最后一项的检查以防止递归调用。 You'll have to implement the method defining whether the last item has been reached or not.您必须实现定义是否已到达最后一项的方法。

EDIT2 : explained lastItemReached EDIT2:解释 lastItemReached

Swift迅速

Method 1: Did scroll to bottom方法1:滚动到底部

Here is the Swift version of Pedro Romão's answer .这是Pedro Romão 答案的 Swift 版本。 When the user stops scrolling it checks if it has reached the bottom.当用户停止滚动时,它会检查它是否已到达底部。

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()
    }
}

Method 2: Reached last row方法2:到达最后一行

And here is the Swift version of shinyuX's answer .这是shinyuX 的答案的 Swift 版本。 It checks if the user has reached the last row.它检查用户是否已到达最后一行。

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()
    }

}

Example of a loadMore() method loadMore()方法示例

I set up these three class variables for fetching batches of data.我设置了这三个类变量来获取批量数据。

// 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

This is the function to load more items from the database into the table view.这是将更多项目从数据库加载到表视图中的功能。

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
            }

        }
    }
}

Better to use willDisplayCell method to check if which cell will be loaded.最好使用willDisplayCell方法来检查是否将加载哪个单元格。 Once we get the current indexPath.row is last we can load more cells.一旦我们得到当前的indexPath.row是最后一个,我们就可以加载更多的单元格。 This will load more cells on scrolling down.这将在向下滚动时加载更多单元格。

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

Details细节

  • Swift 5.1, Xcode 11.2.1斯威夫特 5.1,Xcode 11.2.1

Solution解决方案

Worked with UIScrollView / UICollectionView / UITableView使用 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()
    }
}

Usage用法

init在里面

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

handling处理

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()
                }
            }
        }
    }
}

Full Sample完整样本

Do not forget to paste the solution code.不要忘记粘贴解决方案代码。

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()
                }
            }
        }
    }
}

Result结果

在此处输入图像描述

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

If you are using Core Data and NSFetchedResultsController , then loadMore could look like the following:如果您使用的是 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];
}

Details细节

  • Swift 5.1, Xcode 11.3.1斯威夫特 5.1,Xcode 11.3.1

Solution解决方案

Generic UITableView Extension For Loadmore. Loadmore 的通用 UITableView 扩展。

add this UITableView + Extension in your new file在你的新文件中添加这个 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
    }
} 
}

Now, just add following line of code in UITableViewDelegate Method willDisplay Cell in your ViewController and make sure tableView.delegate = self现在,只需在您的 ViewController 中的 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
    }
}

Result结果

在此处输入图像描述

That's it.. Hope this helpful.就是这样..希望这有帮助。 Thank You谢谢你

I have implemented one solution that i found in stackoverflow, and it works fine, but i think the shinyuX's solution it's very easy to implement and works fine for my propose.我已经实现了我在 stackoverflow 中找到的一个解决方案,它运行良好,但我认为 shinyuX 的解决方案很容易实现并且适用于我的建议。 If someone wants a different solution can use this one below.如果有人想要不同的解决方案,可以使用下面的这个。

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

Use limit and offset in your queries and fill your tableview with that content.在您的查询中使用限制和偏移量,并用该内容填充您的表格视图。 When the user scrolls down, load the next offset.当用户向下滚动时,加载下一个偏移量。

Implement the tableView:willDisplayCell:forRowAtIndexPath: method in your UITableViewDelegate and check to see if it's the last rowUITableViewDelegate中实现tableView:willDisplayCell:forRowAtIndexPath:方法并检查它是否是最后一行

Below link will provide sample code.下面的链接将提供示例代码。 #Swift3 #斯威夫特3

User need to pull up last table view cell, at least hight of 2 cell to fetch more data from server.用户需要拉起最后一个表格视图单元格,至少 2 个单元格的高度才能从服务器获取更多数据。

You will found Process cell also to show loading process as in last cell.您会发现 Process 单元格也显示加载过程,如最后一个单元格。

Its in Swift3它在 Swift3 中

https://github.com/yogendrabagoriya/YBTableViewPullData https://github.com/yogendrabagoriya/YBTableViewPullData

One more option to use ( Swift 3 and iOS 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
             }
         }
     }
 }

For rather small pages (~ 10 items) you might want to manually add data for pages 1 and 2 because nextPage might be somewhere about 1-2 until the table has a few items to be scrolled well.对于相当小的页面(约 10 个项目),您可能需要手动为第 1 页和第 2 页添加数据,因为 nextPage 可能在 1-2 左右,直到表格有几个项目可以很好地滚动。 But it will work great for all next pages.但它适用于所有下一页。

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

very good explanation on this post.这篇文章很好的解释。

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

simple you have to add last row and hide it and when table row hit last row than show the row and load more items.很简单,您必须添加最后一行并将其隐藏,当表格行命中最后一行时,您必须显示该行并加载更多项目。

you should check ios UITableViewDataSourcePrefetching.你应该检查 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)")
    }


}

For Xcode 10.1, Swift 4.2对于 Xcode 10.1、Swift 4.2

This video seems like a great tutorial!这个视频似乎是一个很棒的教程!

Starter/Complete project: https://github.com/RobCanton/Swift-Infinite-Scrolling-Example入门/完成项目: 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()
        })
    }
}

for loading from an API, It works for me, Xcode 10 , swift 4.2 :从 API 加载,它适用于我,Xcode 10 ,swift 4.2

1- create New Swift file and do like this: 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- in Your ViewController file (tableView Controller) : 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()

                }})
        }
    }
}

if using tableView in Your viewController set delegate , datasource self in viewDidLoad.如果在您的 viewController 设置委托中使用 tableView,则在 viewDidLoad 中使用数据源自身。

Just wanna share this approach:只想分享这种方法:

- (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(...); output would be something like:输出将类似于:

<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

This is good for displaying data stored locally.这对于显示本地存储的数据很有用。 Initially I declare the dataLimit to 25, that means uitableview will have 0-24 (initially).最初我将 dataLimit 声明为 25,这意味着 uitableview 将具有 0-24(最初)。

If the user scrolled to the bottom and the last cell is visible dataLimit will be added with 25...如果用户滚动到底部并且最后一个单元格可见dataLimit将添加 25...

Note: This is more like a UITableView data paging, :)注意:这更像是一个 UITableView 数据分页,:)

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

The best way to solve this problem is to add cell at the bottom of your table, and this cell will hold indicator.解决此问题的最佳方法是在表格底部添加单元格,该单元格将包含指标。

In swift you need to add this:您需要快速添加以下内容:

  1. Create new cell of type cellLoading this will hold the indicator.创建 cellLoading 类型的新单元格,这将保存指标。 Look at the code below看看下面的代码
  2. Look at the num of rows and add 1 to it (This is for loading cell).查看行数并将其加 1(这是用于加载单元格)。
  3. you need to check in the rawAtIndex if idexPath.row == yourArray.count then return Loading cell.如果 idexPath.row == yourArray.count 则需要检查 rawAtIndex 然后返回加载单元格。

look at code below:看下面的代码:

import UIKit

class LoadingCell: UITableViewCell {

@IBOutlet weak var indicator: UIActivityIndicatorView!


}

For table view : numOfRows:对于表视图:numOfRows:

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

cellForRawAt indexPath: 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

}

If you notice that my loading cell is created from a nib file.如果您注意到我的加载单元是从 nib 文件创建的。 This videos will explain what I did.这个视频将解释我做了什么。

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
        }
    }
  }

it is sample code.这是示例代码。

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: is visible rows. indexPathArray:是可见的行。

vIndexPath:is visible last indexpath vIndexPath:最后一个索引路径可见

load data加载数据

 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)
           }
       }
        
   }

check your dataArray count which is myadsarray check to equal your data limit.检查您的 dataArray 计数,即 myadsarray 检查是否等于您的数据限制。 then if dataArray count equal next page is called if not equal which is less then 10, all data is showed or finished.然后,如果 dataArray 计数等于下一页,如果不等于小于 10,则调用下一页,则显示或完成所有数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM