iosswiftuirefreshcontrol

UIRefreshControl stuck on return to foreground iOS 10


We have implemented pull down to refresh on our UITableViewController. For a long running refresh if the user goes to the home screen and then returns to the app the UIRefreshControl appears to be stuck - it is still displayed but isn't spinning. Have tried a number of solutions on SO but nothing has worked.

Our implementation:

    //Configure pull to refresh when user "pulls down"
    self.refreshControl?.addTarget(self, action: #selector(NotificationTableViewController.handleRefresh(refreshControl:)), for: UIControlEvents.valueChanged)

    //In handleRefresh we will explicitly start the refresh control if this is a background refresh and not a user initiated pull to refresh 
    refreshControl.beginRefreshing()
    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height - self.topLayoutGuide.length), animated: true)

    //Then once we have reloaded data we stop the refresh in the same manner for user initiated or background refresh (done on main thread)
    refreshControl.endRefreshing()

Solutions we've tried:

Restarting the refreshing when entering foreground when currently refreshing:

if refreshControl!.isRefreshing == true {
     refreshControl!.endRefreshing()
     refreshControl!.beginRefreshing()
     self.tableView.setContentOffset(CGPoint(x: 0, y: - refreshControl!.frame.size.height - self.topLayoutGuide.length), animated: true)
}

... Then the following solutions are about ending the refreshing if we are not supposed to be refreshing. I did try them but in our case we are supposed to be refreshing...

Calling endRefreshing() when entering foreground if not refreshing:

if refreshControl?.isRefreshing == false {
    refreshControl?.endRefreshing()
}

Sending the refresh control to the back on entering foreground:

self.refreshControl?.superview?.sendSubview(toBack: self.refreshControl!)

Does anyone have a fix that works on iOS 10?


Solution

  • What did generally work was to stop the refresh control on background/disappear and start it again on foreground/appear.

    But even this has the issue of a race condition: We have our thread #1 loading data in the background and another thread #2 handling the foreground/appear processing. If thread #1 stops the refresh indicator due to data load completion while at the same time thread #2 attempts to check the state and start the refresh control then we can get ourself in a case where our refresh control is spinning but our load data request already completed. We could attempt to syncronize all this but it's not worth the effort...

    Decided to just stop the refresh control on background/disappear. If the user returns to the screen while the load is going on we don't bother attempting to show the user the refresh control.

        if refreshControl!.isRefreshing == true {
            refreshControl!.endRefreshing()
        }