Add a ActivityIndicator to the bottom of UITableView while loading
Asked Answered
G

6

28

I want to add a ActivityIndicator to the bottom of my UITableView when the last cell is displayed, while I'm fetching more data, then when the data got fetched hide it.

So I scroll to the bottom -> last row displayed -> spinner starts spinning while data is fetched -> Data fetched, hide the spinner -> new data added to the tableview.

Any tips on how can I achieve this?

Thanks ;)

Giorgi answered 25/2, 2017 at 14:33 Comment(0)
C
71

Add This Function

 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let lastSectionIndex = tableView.numberOfSections - 1
    let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1
    if indexPath.section ==  lastSectionIndex && indexPath.row == lastRowIndex {
       // print("this is the last cell")
        let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        spinner.startAnimating()
        spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))

        self.tableview.tableFooterView = spinner
        self.tableview.tableFooterView?.isHidden = false
    }
}

and tableFooterView should hide when data load.

Citronellal answered 25/2, 2017 at 14:53 Comment(5)
Never use fix size values, because of different screen sizesTranslatable
What is tableView_category?Translatable
the tableview_category is your default tableView which contains the rows. I got that and this example worked like a charmGiorgi
it works but loading is not stop when reached at the endTremann
loading is not getting stopped when it is at bottom, thus run service multiple timesAperiodic
M
17

Swift 5

 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let lastSectionIndex = tableView.numberOfSections - 1
    let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1
    if indexPath.section ==  lastSectionIndex && indexPath.row == lastRowIndex {
       // print("this is the last cell")
        let spinner = UIActivityIndicatorView(style: .medium)
        spinner.startAnimating()
        spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))

        self.tableview.tableFooterView = spinner
        self.tableview.tableFooterView?.isHidden = false
    }
}
Michaelemichaelina answered 1/8, 2018 at 9:35 Comment(2)
Worked like a charmLarainelarboard
Add these two lines, when you the data loads -- self.tableView.tableFooterView?.isHidden = true self.tableView.tableFooterView = nilIllbred
K
9

Update 2020 Swift 5, Xcode 11

I used @Ayaz Akbar's solution and it worked like charm. It just needed some improvements. Here is how I adopted it to my needs.

Removing the loading indicator if no more items to load

Since you are adding the loading indicator every time you go to the end of the items there will be the case where you no longer add new items to the table view data source. At this point you need to remove the loading indicator. I am using a custom UITableViewDataSource which calls its completion handler every time new data is received. I also keep a var newCount to keep track of the new items count. So here is what I do in my view controller to hide the activity indicator:

private lazy var dataSource: CustomTableViewDataSource = {
    return CustomTableViewDataSource(query: CustomQuery) { [unowned self] (result) in
    // For the initial load I needed a standard activity indicator in the center of the screen
    self.stopMainActivityIndicatorIfNeeded()

    switch result {
    case .success(()):
        if self.dataSource.newCount > 0 {
            self.tableView.reloadData()
        } else {
            // Hide the tableFooterView, respectively the activity indicator
            self.tableView.tableFooterView = nil
        }
    case .failure(let error):
        self.showError(error)
    }
    }
}()

Here is my updated UITableView delegate method:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let lastSectionIndex = tableView.numberOfSections - 1
    let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1
    if indexPath.section ==  lastSectionIndex && indexPath.row == lastRowIndex && dataSource.newCount > 0 {
        let spinner = UIActivityIndicatorView(style: .gray)
        spinner.frame = CGRect(x: 0.0, y: 0.0, width: tableView.bounds.width, height: 70)
        spinner.startAnimating()
        tableView.tableFooterView = spinner
    }
}
Keewatin answered 30/1, 2020 at 10:53 Comment(4)
What is going on in the initializer CustomTableViewDataSource(query: CustomQuery)? Maybe you could add the code for CustomTableViewDataSource in your answer?Sibilate
The initializer is something like this? init(query: CustomQuery, completionHandler: @escaping (Result<(), Error>) -> Void) { ... }Sibilate
Something similar. Just adjust Result enum to your needs. My completion handler looks like this: @escaping (Result<Void>) -> Void. If you would like to learn more about this technique and the data source check this out: codelabs.developers.google.com/codelabs/…Keewatin
I needed to change it to @escaping (Result<Void, Void>) -> Void to not get syntax errors.Sibilate
C
2

One way would be to add a custom cell with a UIActivityIndicatorView in it. You could put it in a separate section. There are many ways to do it.

Or you check this out: https://github.com/evnaz/ENFooterActivityIndicatorView

Clamper answered 25/2, 2017 at 14:42 Comment(3)
I'll give it a lookGiorgi
I wouldn't like to use a third-party library just for that, and the given example is written in Obj-CGiorgi
You don't have to use it. You can just check it out to know how it works and write it in swift if you need to.Translatable
D
0
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    let total = (self.array.count )
    if (indexPath.item + 1) == (self.array.count ){
        self.limit = Int(self.limit+20) // table item render limit
        if total < limit {
            let spinner = UIActivityIndicatorView(style: .gray)
            spinner.startAnimating()
            spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))

            self.myTableView.tableFooterView = spinner
            self.myTableView.tableFooterView?.isHidden = false
        }
    }
}
Disrespectable answered 21/4, 2019 at 11:55 Comment(0)
J
0

You can add subview to UITableView and show it when user scrolls to bottom.

I had to implement same thing on multiple UIScrollViews and ended up creating a DSL for it.

Also created a pod
You can take a look at the source code or use the pod if you want.

https://github.com/salmaanahmed/PullToRefreshDSL

Jarv answered 7/1, 2021 at 8:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.