NSProgressIndicator usesThreadedAnimation broken on High Sierra

NSProgressIndicator's -usesThreadedAnimation feature is completely broken on High Sierra. I use this in a few areas for indeterminate progress indicators, for instance inside a template icon when the user has chosen to create a project from a template and the template is being extracted to disk. On High Sierra, the progress indicator appears but does not spin, sitting there still during the blocking process.


I've reported this as a bug, #32929906, which has been closed as a duplicate of #10463742. The bug has been there since beta 1 and is still present in beta 4. When I updated my bug report to say that it was still present, Apple engineering replied, "it has not been determined how or when the issue will be resolved". Hopefully this is just a polite way of saying, "Yeah, we know, stop hassling us", but in case this isn't fixed for the release, does anybody know of a workaround? This seems like quite a major bug, but I can't find anyone else mentioning it anywhere. Is there a trick to getting an indeterminate NSProgressIndicator working using threaded animation on High Sierra that I've missed?

Unfortunately, this bug is still present in 10.13 beta 7, and given that the rumours are that 10.13 will be released next month, it looks as though I cannot rely on it being fixed in time for release (in which case -usesThreadedAnimation will be nonfunctional in 10.13). Before I look to replacing NSProgressIndicator with a custom progress indicator class that can successfully animate while work is being done on the main thread (such as ITProgressIndicator), has anyone here got any ideas or successfully got this working? Here's all you need to do to see the bug:


self.progressIndicator.usesThreadedAnimation = YES; // self.progressIndicator = NSProgressIndicator instance.

[self.progressIndicator startAnimation:nil];

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]; // Block main thread for three seconds.

[self.progressIndicator stopAnimation:nil];


The above will show a spinning progress indicator during the three seconds of main thread blocking on all versions of macOS up to and including 10.12; on 10.13 the progress indicator either stays still or disappears.

I don't want to sound unsympathetic, but when was it ever really acceptable to display a spinning progress indicator (properly animating) and block the main thread for extended periods, meaning a couple of seconds or more?


In the case where your UI (I'm assuming it's something like a list of things, each of which has an icon, but it's the same point if it's something else) doesn't have an icon available, it make sense to display a progress indicator (properly animating), but why on earth should that need to block the main thread?


If you have a scenario that's truly modal, in the user cannot proceed yet, it's still better to:


a. Display a separate progress dialog (which, by changing the first responder chain to omit your main UI, disables pretty much all the menu items without any work) that actually runs modally.


b. Put a "Cancel" button in the dialog, which implies that your main thread can't block, or it won't actually be cancellable.


c. Move the actual work to a background thread, with a completion handler running back on the main thread.


I tried your code, changing the time from 3 to 30 seconds. After the first 5 seconds (approx) the spinning pizza of death appears (of course), which is a good clue that your old solution is Doing It Wrong™.


Or, last fallback, if you really really want to do it your way, then a different solution (such as the github project you linked) seems like the correct way to go.

-usesThreadedAnimation has been a feature of NSProgressIndicator that has allowed this to work for years - I was really hoping a way to work around the bug that is preventing it from working on macOS 10.13. Obviously my sample code is contrived and not something you would do in real life, and blocking the UI isn't something I would normally want to do, but there are occasions where this feature of NSProgressIndicator is useful and the sample code does show the bug with NSProgressIndicator on 10.13.


In the particular situation I cited in my original post, I have a template panel such as the one you get in Pages or iBooks Author. You choose a template, choose a place to save the project to disk, and then a spinner appears inside the template's icon as the template is extracted to disk using unzip in an NSTask. This takes a second or two at most, but still the spinner is a useful clue to show that the app is loading the project file. It's not particularly unusual - you can see similar behaviour in other programs, and in fact it is very similar to (and based on) the spinner that appears in Apple's own iBooks Author when you create a book from a template there.


In this instance I suppose I could move the NSTask to a background thread, but in this particular case doing so just doesn't gain anything (except working around the 10.13 bug). That might be the way to work around the bug if it isn't fixed for release, though...

I have no problem whatsoever with your UI. What doesn't make sense to me why you would block the main thread at the point where (say) the icon is being extracted: the spinner is there to indicate that the extraction isn't finished yet, not that the app is blocked. The issue is the main thread blocking, regardless of what appears in the UI during those couple of seconds.


>> I could move the NSTask to a background thread, but in this particular case doing so just doesn't gain anything


But it does gain something, it unblock the main thread. Again, not because of the spinner issue, but because modern Mac apps don't block the main thread. Unzipping something seems like a perfect candidate for a background thread action.


The point is that the main thread run loop is a clearing house for all sort of events/sources that pop into relevance at unpredictable times. It's highly desirable for your main thread run loop to cycle without hitches. There are historical reasons why there used to be less emphasis on this, but that has changed over time. Indeed, it's almost unheard of, now, for Apple to introduce any new synchronous APIs for long-running functions.

In this case, not blocking the main thread for a second would be rather odd. We're talking about unzipping preset files of a few kilobytes only, not something that is going to block for more than a second or two, and it's essentially a "prepare template and open the new project" action - exactly the same action as when you, say, double-click on a template in Pages or iBooks Author or Numbers, all of which are modern Mac apps that block in this exact same situation, for the second it takes to create and open the document. (Pages used to show a spinner like iBooks Author still does, as I recall, but seems no longer to do so - resulting in a rainbow wheel and a wait for a second or more with no indicator at all for some of the bigger templates, such as newsletters, which is exactly why I prefer a spinner, to avoid this pause where nothing happens as the project is created and opened. For a more egregious example that really does block the UI for too long, try creating a new calendar in Photos...)


Anyway, regardless of whether this one particular example is or is not bad practice, I'd love to find a workaround for the 10.13 bug with -usesThreadedAnimation. 🙂 I guess I'll just have to wait and see if it is fixed or move to a third-party solution.

I load data in off a background queue a little at a time, dispatching to the main queue for each item loaded. Since it is a indeterminate amount of data my app could be loading, it can finish very quickly (or not). But usually it will finish quickly (a few seconds).


But dispatching on the main queue sometimes blocks the main thread (because it happens so fast) and the NSProgressIndicator does not animate. You can see it, sometimes for 4-5 seconds and everything finishes. If I sleep between dispatching to the main queue:


sleep(1);

dispatch_async(dispatch_get_main_queue(), ^{
  //Loaded in some.
});


Progress spins and problem is solved. Which is fine. Adding that sleep really makes a difference from a visual standpoint. It doesn't look right to have a spinner stuck. I think it'd be nice if the usesThreadedAnimtion property worked, wouldn't surprise me if they deprecate it though.

NSProgressIndicator usesThreadedAnimation broken on High Sierra
 
 
Q