Saturday, January 26, 2013

Unexpected behaviour when calling setProgress:animated: twice in the same run loop

I found some bugs in setProgress:animated: method of UIProgressView. It seems that calling it several times within the same run loop ignores all but the last call.

For instance, I have a bar set up in the storyboard with the progress all the way to the max (i.e. 1.0). If later in the code I do this:

- (IBAction)showBug {
    [self.progressBar setProgress:0.0 animated:NO];
    [self.progressBar setProgress:0.5 animated:YES];

I would expect the bar to jump to 0 and then animate to 0.5. What happens instead, the bar animates from 1.0 to 0.5.

One workaround, of course, is to place the 2nd call on a separate run loop, like so:

- (void)hack
    [self.progressBar setProgress:0.5 animated:YES];

- (IBAction)fixIt {
    [self.progressBar setProgress:0.0 animated:NO];
    [self performSelector:@selector(hack) withObject:nil afterDelay:0];

Ahh, the trusted 0 timer. How many times it saved my skin. For instance, when mutating collections while iterating. And not only in Objective-C, I remember using it in JavaScript to work around all kinds of browser bugs. That's the advantage of having exposure to multiple platforms and languages. You learn all kinds of tricks.

Incidentally, when I was preparing the bug report for Apple, I discovered yet another, unrelated bug. Placing this line:

[self.progressBar setProgress:0.5 animated:YES];

in the - (void)viewDidLoad method of the view controller makes another mess. Now the bar, which was placed statically in the storyboard, (in my example I had it centred) it animates when the app starts, through a "genie" animation from the top-left. Nice, but definitely not what I intended.

Update: Regarding the latter bug, Apple informed me that  "The view animates from an undefined state. Doing such things in the viewDidLoad can only lead to problems.". See this post for a discussion of the perils of reading/writing the frame of a view in viewDidLoad.

No comments: