UITableView load more when scrolling to bottom like Facebook application

后端 未结 18 2131

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

提交回复
热议问题