UITableView load more when scrolling to bottom like Facebook application

后端 未结 18 2050

I am developing an application that uses SQLite. I want to show a list of users (UITableView) using a paginating mechanism. Could any one please tell me how to load more dat

相关标签:
18条回答
  • 2020-11-27 09:29

    You can do that by adding a check on where you're at in the cellForRowAtIndexPath: method. 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

    0 讨论(0)
  • 2020-11-27 09:30

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

    If the user scrolled to the bottom and the last cell is visible dataLimit will be added with 25...

    Note: This is more like a UITableView data paging, :)

    0 讨论(0)
  • 2020-11-27 09:31

    Better to use willDisplayCell method to check if which cell will be loaded. Once we get the current indexPath.row is last we can load more cells. 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.
    }
    
    0 讨论(0)
  • 2020-11-27 09:31

    Details

    • Swift 5.1, Xcode 11.2.1

    Solution

    Worked with 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

    0 讨论(0)
  • 2020-11-27 09:31

    Details

    • Swift 5.1, Xcode 11.3.1

    Solution

    Genetic UITableView Extension For Loadmore.

    add this UITableView + Extension in your new file

    extension UITableView{
    
        func indicatorView() -> UIActivityIndicatorView{
            var activityIndicatorView = UIActivityIndicatorView()
            if self.tableFooterView == nil{
                let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 40)
                activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
                activityIndicatorView.isHidden = false
                activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
                activityIndicatorView.isHidden = 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()
                    }
                }
            }
            indicatorView().isHidden = false
        }
    
        func stopLoading(){
            indicatorView().stopAnimating()
            indicatorView().isHidden = true
        }
    }
    

    Now, just add following line of code in UITableViewDelegate Method willDisplay Cell in your ViewController and make sure 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

    0 讨论(0)
  • 2020-11-27 09:32
    -(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];
                });
            }
        }
    }
    }
    
    0 讨论(0)
提交回复
热议问题