UIRefreshControl Stuck After Switching Tabs in UIT

2019-01-17 20:51发布

问题:

I have a UITableViewController as the root view controller in a UINavigationController, which is in turn one of the view controllers in a UITabBarController. All hooked up in Storyboard.

I've configured the UIRefreshControl for my table in Storyboard as well, and normally it looks like it should when pulling:

However, if I switch between my other tabs once or twice, it looks like this:

It's not spinning or anything, just stuck "full", and it stays that way until I pull fully and trigger a refresh.

Any ideas or suggestions appreciate.

回答1:

user2009606 was almost right, I fixed it by calling

[refreshControl endRefreshing];

On

viewWillAppear:

The refreshControl now behaves properly after switching tabs, independent of its state.



回答2:

I know this is incredibly late, but I figure better late than never.

Unfortunately, none of the given answers worked for me. The only thing that worked was this awful piece of code in viewWillAppear:

self.refreshControl = nil;
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refreshFunction) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;

Basically I recreate the UIRefreshControl every time the view is going to appear. It works, albeit while giving the following warning:

Attempting to change the refresh control while it is not idle is strongly discouraged and probably won't work properly.

I'm honestly surprised this is still an issue (still seeing this in iOS 9 now). Hopefully this may be helpful to some other people.



回答3:

The following solution may help you.

CGFloat yTableViewOffset;

- (void)viewDidLoad {

      yTableViewOffset = kNilOptions;
}
- (void)viewWillAppear:(BOOL)animated
{
   if(yTableViewOffset != kNilOptions) {

     CGPoint offset = CGPointMake(tableView.contentOffset.x, yTableViewOffset);
     [refreshControl beginRefreshing];
     tableView.contentOffset = offset;

  }
}
- (void)viewWillDisappear:(BOOL)animated
{
   if(refreshControl.isRefreshing) {
     yTableViewOffset = tableView.contentOffset.y;
     [refreshControl endRefreshing];
  }
}


回答4:

To sum up and provide a complete solution.

The problem:

If UIRefreshControl is animating during screen transition, it will stay visible but will not be animating. Video https://vid.me/Bkb7

Also, if pull to refresh animation was started but not completed, and then transition was performed, UIRefreshControl will be hidden but stuck. Video https://vid.me/Bkb7

Solution:

Start UIRefreshControl animation on viewWillAppear and end it on viewDidDisappear. Save the state of refresh process to know when to show UIRefreshControl.

Bellow is the whole solution that also handles the proper animation and simple pull to refresh animation.

Add to UITableView or subclass

Initialization:

 /// Refresh indicator
var refreshControl = UIRefreshControl()

/// Refresh state
var isRefreshing = false

/// Refresh controll update funcion. Set to enable pull to refresh
var refreshControllUpdateFunction: (() -> ())?

/// Prepeares UIRefreshControll. Call from init
func initRefreshControl(){
    addSubview(refreshControl)

    refreshControl.addTarget(self, action: "refreshControllTarget", forControlEvents: .ValueChanged)
    refreshControl.beginRefreshing()

    isRefreshing = true
}

Superview event handlers:

/// Call on viewWillApper
func superviewWillApper(){
    if isRefreshing && !refreshControl.refreshing{
        startRefreshAnimation()
    }
}

/// Call on viewDidDisapper
func superviewDidDisappear(){
    endRefreshAnimation(false, dataFetched: !isRefreshing)
}

UIRefreshControl animation handler:

/// Presents animating UIRefreshControll
func startRefreshAnimation(){
    refreshControl.beginRefreshing()
    contentOffset = CGPointMake(0, -refreshControl.bounds.size.height)

    isRefreshing = true
}

/// Hides UIRefreshControll and saves state of refresh
func endRefreshAnimation(wasEmpty: Bool, dataFetched: Bool){
    refreshControl.endRefreshing()

    isRefreshing = !dataFetched

    if !wasEmpty{
        setContentOffset(CGPointZero, animated: true)
    }else{
        setContentOffset(CGPointZero, animated: false)
    }
}

Add

table.isRefreshing = true

to begining of the refresh call.

Result video https://vid.me/LyuI



回答5:

Simply calling endRefreshing in viewWillDisappear: fixed all the issues for me.

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.refreshControl endRefreshing];        
}

Doing the same thing in viewWillAppear: caused the refresh control to rotate strangely when pulling the table.



回答6:

I have just seen the same thing. What I ended up doing is endRefreshing on viewWillDisappear and that at least eliminates this problem. However, coming back to the tab I expect it to start spinning again as I restart the refresh process and and call beginRefreshing again, but it does not. It does in the case when I drill down navigation and then come back though.



回答7:

Nicest fix in Swift:

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

  //fixes bug with refreshControl freezing while switching tabs
  if tableView.contentOffset.y < 0 {
    tableView.contentOffset = .zero
  }
}


回答8:

While refresh control is spinning, if you present some other controller and come back before "end refreshing"; refresh animation will be stuck.

None of the answers above worked for me, and I didn't want to set bool and follow it through all lifecycle of a view controller.

My solution is adding below code to end of viewWillAppear.

in Objc;

if (self.refreshControl.isRefreshing) {
    [self.refreshControl endRefreshing];
    [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated: true];
    [self.refreshControl beginRefreshing];
}

in Swift;

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

Logic is; whenever it catches refresh control is in the state of animating, it will stop it without knowing it is stuck or not and re-run the animation.

Hope it helps.



回答9:

I have a collectionView, and I did set it's contentInset.

When pushing some ViewControllers, then press home button, and go back and pop back, the UIRefreshControl is always on the top.

Finally I use this code temporarily avoid it. (Ugly but stops it always shows on top)

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [self.refreshControl endRefreshing];

    CGPoint currentContentOffset = self.collectionView.contentOffset;

    [self.collectionView setContentOffset:CGPointMake(currentContentOffset.x,
                                                      currentContentOffset.y - 1.0f)
                                 animated:YES];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    CGPoint currentContentOffset = self.collectionView.contentOffset;

    [self.collectionView setContentOffset:CGPointMake(currentContentOffset.x,
                                                      currentContentOffset.y + 1.0f)
                                 animated:YES];
}


回答10:

I want some points and like Albert solution but not his style.

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _table.contentOffset = CGPointMake(_table.contentOffset.x, _table.contentOffset.y + 1.0f);
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    _table.contentOffset = CGPointMake(_table.contentOffset.x, _table.contentOffset.y - 1.0f);
}


回答11:

Hope I can help solving this issue finally :

There is an issue using UITableView with a Pull-To-Refresh control. This can be EASILY solved, by adding a UITableViewController.

UITableViewController *tableController = [[UITableViewController alloc] init];
    tableController.automaticallyAdjustsScrollViewInsets = NO;
    [tableController willMoveToParentViewController:self];
    [self addChildViewController:tableController];
    tableController.tableView = self.containerTableView;
    tableController.refreshControl = self.refreshControl;

This way you just wrap your UITableView inside a controller for which the [self.refreshControl endRefreshing] will work completely fine inside viewWillAppear or viewWillDisappear. In some cases you might need to call it in both.

Hope this helps! :)



回答12:

I could not get the call to endRefreshing to work before the navigation happened so I approached the problem from the other end and on viewWillAppear I check if the tableView contentOffset is a negative number bring the tableView back to 0.

 - (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if (self.tableView.contentOffset.y < 0)
    {
        [self.tableView setContentOffset:CGPointZero animated:YES];
    }
}


回答13:

The answers in this question solved my problem

basically stopping and starting the spinner again on viewWillAppear



回答14:

I was able to solve this issue by placing the following piece of code in viewWillDisappear

    DispatchQueue.main.async {
        self.refreshControl.beginRefreshing()
        self.refreshControl.endRefreshing()
    }

If the above code is placed in viewWillAppear then spinner will animate and dismiss within a fraction of seconds, in either of the cases the issue will be solved but however, in users point of view the spinner should not be visible so, it's better to place it in viewWillDisappear.



回答15:

You can try

refreshControl.beginRefreshing()
refreshControl.endRefreshing()

in viewWillAppear, and this should work.



回答16:

After running into the same problem several times I decided to make a library for it SwiftPullToRefresh.

As said before, refreshControl needs to be stoped on viewDidDisappear(_:) and started on viewWillAppear(_:).

When programatically starting refreshControl animation, tableView.contentOffset needs to be set to appropriate value.

tableView.setContentOffset(CGPoint(x: 0, y: -(refreshControl?.bounds.height ?? 60) - topOffset), animated: true)

var topOffset: CGFloat {
    if let navigationBar = navigationController?.navigationBar, navigationBar.isTranslucent == true {
        return navigationBar.bounds.height + UIApplication.shared.statusBarFrame.height
    } else {
        return 0
    }
}