UIRefreshControl bug when entering foreground

2019-04-04 08:19发布

问题:

I've noticed a little bug (but really annoying) when I use UIRefreshControl in my View Controller. When the application returns from the background the UIRefreshControl is already loaded and it looks like this:

As you can see I use a custom navigation controller which hides like in the Facebook app (AMScrollingNavBar). When I reload data in UITableView everything comes back to normal and this bug shows only after returning from the background.

This is the code which I use to initialize UIRefreshControl in viewDidLoad:

// Initializing generic refresh control
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(collectData) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:self.refreshControl];

回答1:

This is a known bug in iOS7. You can see it reproduce in Apple's mail app. I can confirm it has not been fixed as of iOS7.1 beta 5 iOS8.0 beta 3 iOS 10.0.1.

First, open a bug report at https://bugreport.apple.com/ My radar number is rdar://14586451, which is a duplicate of rdar://14493713 (still open).

A proposed fix is to register for UIApplicationWillEnterForegroundNotification notifications in your view controller and call [self.refreshControl.superview sendSubviewToBack:self.refreshControl]; to somewhat remedy the issue by having the refresh control appear behind your table content.

I see in the second screenshot that the refresh control shows under your cell. This is likely because you have set a clear color as your cell's background. Set it to white.



回答2:

I found one workaround for a variation of this bug. It might potentially help with yours, too. In my app, switching to another tab and going back to the tab with the refresh control broke it - the control didn't animate any more while dragging.

Instead of setting up UIRefreshControl in viewDidLoad, I set it up in viewDidAppear, and then always remove it in viewDidDisappear. This way it's always initialised fresh and doesn't get confused.

As in the previous answer to this question, i've requested UIApplicationDidBecomeActiveNotification so that the refresh control can be fixed also when the user jumps back in the app.

@implementation MYViewController {
    UIRefreshControl *refreshControl;
}

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

    /* Pull down to refresh. iOS bug: If we add this in viewDidLoad and let it
     * stay there for the lifetime of the view, the control will misbehave
     * after going to another view/tab and coming back (will not animate nicely
     * on drag).
     */
    [self setupRefreshControl];

    /* We'll want to be notified for didBecomeActive, to do the pull-down-to-refresh workaround when resuming. */
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(setupRefreshControl)
            name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)setupRefeshControl
{
    if (refreshControl)
        [refreshControl removeFromSuperview];

    refreshControl = [[UIRefreshControl alloc] init];
    [refreshControl addTarget:self action:@selector(refreshPull:)
        forControlEvents:UIControlEventValueChanged];

    [scrollView addSubview:refreshControl];
}

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

    [refreshControl removeFromSuperview];
    refreshControl = nil;

    /* We don't need notification for becoming active any more */
    [[NSNotificationCenter defaultCenter] removeObserver:self
        name:UIApplicationDidBecomeActiveNotification
      object:nil];
}

Slightly suboptimal, but works.



回答3:

This bug seems to occur when making a modal presentation over the table view controller as well as when the app returns from the background.

The simplest fix is to call endRefreshing() in viewWillAppear(_:) and upon receiving the notification UIApplicationWillEnterForeground. You need to do both because viewWillAppear(_:) is not called when the app returns from the background.

The effect of calling endRefreshing() on an instance of UIRefreshControl appears to be to return the control to the correct location in the view hierarchy and ensure that it continues to animate correctly on subsequent refreshes.

Remember to check that your refresh control is, in fact, not refreshing.

In Swift:

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

    endRefreshing()

    NotificationCenter.default.addObserver(self,
        selector: #selector(endRefreshing),
        name: Notification.Name.UIApplicationWillEnterForeground,
        object: nil)
}

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

    NotificationCenter.default.removeObserver(self,
        name: Notification.Name.UIApplicationWillEnterForeground,
        object: nil)
}

func endRefreshing(_ notification: Notification? = nil) {
    if refreshControl?.isRefreshing == false {
        refreshControl?.endRefreshing()
    }
}

Tested in Xcode 7.0 targeting iOS 8.0 with a UITableViewController configured in the storyboard.