3

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?

Community
  • 1
  • 1
Marcus Leon
  • 55,199
  • 118
  • 297
  • 429
  • can you please your implementation for the UIRefreshControl? – hasan Mar 18 '17 at 14:34
  • added our implementation details – Marcus Leon Mar 18 '17 at 14:49
  • 1
    did you check this? http://stackoverflow.com/questions/24341192/uirefreshcontrol-stuck-after-switching-tabs-in-uitabbarcontroller – hasan Mar 18 '17 at 14:54
  • No I will try that later today thanks – Marcus Leon Mar 18 '17 at 14:56
  • Why are you explicitly setting content offset of the table? The refresh control is usually set as the tableHeaderView and the table will automatically handle the pull to refresh and the content offset. Then you can explicitly call beginRefreshing beginAnimations, endAnimations, endRefreshing from the background, and the table will take care of the rest. When you explicitly set the content offset, then you also need to set it back to 0 when you are finished refreshing. I do not see that in the code above. – Brandon Mar 18 '17 at 14:58
  • I only do that when I initiate a background refresh when the app returns to foreground. It's needed to show the refresh control. I don't do that when the user initiates the refresh by pulling down. The iOS API seems to take care of the offset for you in this case. See http://stackoverflow.com/a/14719658/47281 – Marcus Leon Mar 18 '17 at 15:03
  • Did you set it back to 0 when you are finished refreshing? – Brandon Mar 18 '17 at 15:04
  • I don't `endRefreshing()` seems to take care of that. – Marcus Leon Mar 18 '17 at 15:04
  • Check this out https://github.com/Rep2/SwiftPullToRefresh – Ivan Rep Nov 25 '17 at 16:08

3 Answers3

2

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()
    }
Community
  • 1
  • 1
Marcus Leon
  • 55,199
  • 118
  • 297
  • 429
1

If refreshControl!.isRefreshing is false but you still have that glitch use this:

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)

  //fixes bug with refreshControl freezing while switching tabs

  if tableView.contentOffset.y < 0 {
    tableView.contentOffset = .zero
  }
}
0

Use this To prevent refreshControl freezing while switching tabs (supports contentInsetAdjustmentBehavior = .automatic)

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if tableView.contentOffset.y < -tableView.safeAreaInsets.top {
        refreshControl.endRefreshing()
        tableView.contentOffset = CGPoint(x: 0, y: -tableView.safeAreaInsets.top)
    }
}
Dan Dolog
  • 241
  • 3
  • 3