Swift iOS:如何在UITableView中添加无限滚动分页。

18

我想知道tableview是否有内置函数来添加无限滚动/分页功能。

现在我的VC看起来像这样:

var data: JSON! = []

override func viewDidLoad() {
    super.viewDidLoad()

    //Init start height of cell
    self.tableView.estimatedRowHeight = 122
    self.tableView.rowHeight = UITableViewAutomaticDimension

    self.tableView.delegate = self
    self.tableView.dataSource = self

    savedLoader.startAnimation()
    //Load first page
    loadSaved(1)

}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier("aCell") as! SavedTableViewCell

    let info = data[indexPath.row]
    cell.configureWithData(info)

    return cell
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    performSegueWithIdentifier("WebSegue", sender: indexPath)

    tableView.deselectRowAtIndexPath(indexPath, animated: false)
}

我使用 loadSaved(1) 从中获取数据,通过给该函数提供要加载的当前页面。该函数使用alamofire进行API请求,然后将应显示的数据填充到var data:JSON!=[]。

所以当我滚动到tableview底部时,我想要做的是调用loadSaved(2),将更多数据加载到tableview中。


我的回答可以帮助你,链接在这里:https://dev59.com/bF4c5IYBdhLWcg3wLXga - Victor Sigler
https://github.com/canopas/MarqueeScroll - SPatel
7个回答

29

看起来像是amar的答案现在可能是更好的解决方案,但这是我2017年的回答:

UITableViewDelegate有一个tableView(_:willDisplay:forRowAt:)实例方法,它“告诉代理表视图将要为特定行绘制单元格。”

在你的情况下,我会像这样使用它:

override open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row == data.count-1 { //you might decide to load sooner than -1 I guess...
      //load more into data here
    }
}

根据您的代码,您可能需要在此周围进行一些检查,以确保如果您已加载所有数据,您不会陷入无限循环中...


1
我在 Swift 5 上尝试了这种方法,它完美地运行了。 - Ste
@Ste,你试过Amar的答案了吗?https://dev59.com/TFkS5IYBdhLWcg3w-K3l#54457661 - lukkea

7
不,UITableView没有任何内置功能来实现无限滚动或按需加载单元格。您可以使用默认实现在UITableView中的UIScrollViewDelegate函数scrollViewDidScroll(_:),以此方式知道用户是否滚动超过了在UITableView中定义的原始高度。
例如,像这段代码一样:
var indexOfPageToRequest = 1

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // calculates where the user is in the y-axis
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height

    if offsetY > contentHeight - scrollView.frame.size.height {

        // increments the number of the page to request
        indexOfPageToRequest += 1

        // call your API for more data
        loadSaved(indexOfPageToRequest)

        // tell the table view to reload with the new data
        self.tableView.reloadData()
    }
}

为了在UITableView的末尾添加其余元素,您应该将新元素添加到数据源中,在您的函数loadSaved(numberOfPage)中使用data。希望这可以帮助您。

6
所有以上答案都是正确的,但对于iOS 10及以上版本,我们有一个非常好的
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])

这是一个需要设置的预取代理。
tableView.prefetchDataSource = self

RayWeinderlich上有一个关于此主题的很好的教程。由于Rays是一个可靠的网站,我不会在这里发布代码。


4
我修改了Victor的答案并将其用作...
var indexOfPageRequest = 1
var loadingStatus = false

func loadData(){
    if !loadingStatus{
        loadingStatus = true
        viewModel.getData(pageIndex: indexOfPageRequest)
    }
}

override func scrollViewDidScroll(_ scrollView: UIScrollView) {

    // calculates where the user is in the y-axis
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height

    if offsetY > contentHeight - scrollView.frame.size.height {

        // increments the number of the page to request
        indexOfPageRequest += 1

        // call your API for more data
        loadData()

        // tell the table view to reload with the new data
        self.tableView.reloadData()
    }
}

当您接收到数据时,请将loadingStatus重置为true。没有检查视图是否已经加载了更多数据,表格视图会闪烁。


如果我们的TableView不是从顶部开始,而是从底部开始怎么办?在这种情况下,我该如何进行这种检查? - Faruk
这种方式会更加消耗 CPU。最好先检查用户在哪一行,然后再检查他滚动到了哪里。 - Lachezar Todorov

2
Ravi的回答看起来很不错。但是正如他最后指出的那样,如果你使用scrollViewDidScroll(_ scrollView: UIScrollView),tableView会闪烁得很厉害。
这是因为每次滚动tableView时都会尝试重新加载tableView。
相反,您可以使用scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)委托方法来确定是否已经滚动足够并且已经接近tableView的末尾。
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if offsetY > contentHeight - scrollView.frame.size.height {
               indexOfPageRequest += 1
               loadData()
               self.tableView.reloadData()
        }

    }

1
上述答案也是正确的,但可能有人可以帮助改进这段代码。
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){
    if(indexPath.row == self.arryOfData.count-1){
        if(self.pageNumber <= self.resPgNumber){
            if(remaining != 0){
                let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
                spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
                spinner.startAnimating()
                tableView.tableFooterView = spinner
                tableView.tableFooterView?.isHidden = false

                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    self.flgActivity = false
                    self.getActiveOrdersList()
                }
            }
            else{
                tableView.tableFooterView?.removeFromSuperview()
                let view = UIView()
                view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
                tableView.tableFooterView = view
                tableView.tableFooterView?.isHidden = true
            }
        }
        else{
            tableView.tableFooterView?.removeFromSuperview()
            let view = UIView()
            view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
            tableView.tableFooterView = view
            tableView.tableFooterView?.isHidden = true
        }
    }
    else{
        tableView.tableFooterView?.removeFromSuperview()
        let view = UIView()
        view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
        tableView.tableFooterView = view
        tableView.tableFooterView?.isHidden = true
    }
}

我喜欢你检查应该显示的行的方式,但是为什么要在3个地方添加相同代码的隐藏视图呢? - Lachezar Todorov

0

有许多方法可以实现这个。所有不同方法的本质是,在用户滚动到底部时加载下一组数据。我通过在tableView末尾添加一个特殊的额外单元格来实现它,当该单元格在willDisplay cell: forRowAtIndexPath:中加载时,触发下一组数据的获取。

尽管这很容易实现,但在更大的应用程序中,有时我们需要在许多地方实现它。为了避免这种情况,我编写了一个小的框架,它是非侵入性的,并且可以轻松集成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接