I have a bar-style NSProgressIndicator in a subview of a layer-backed view. Its behavior is sort of complicated, but at certain points, it is displayed as a bar-style indeterminate progress indicator. The problem is, when in this state, it doesn’t animate (i.e. twirl the barber pole). Turning off layer backing fixes the issue, but that makes other animations the window does less smooth, so I’m hoping for something better.
Here’s the full behavior: when what amounts to a dirty flag is set, it should become visible as an indeterminate, animating progress indicator; then after a short delay (to make sure the user has finished typing) it transforms into a determinate progress indicator and fills as various operations are performed; and finally, at the end of the whole process, it hides itself once more.
To implement this, I’ve set up the following bindings:
- Hidden is bound to my model’s
loadingproperty with anNSNegateBooleanvalue transformer. - Is Indeterminate is bound to my model’s
waitingForInputproperty. - Value is bound to my model’s
currentProgressproperty (which is 0 whenwaitingForInputis true). - Max Value is bound to my model’s
maximumProgressproperty (which is 0 whenwaitingForInputis true).
This mostly works, but with one exception: when waitingForInput is YES, and thus the progress indicator is indeterminate, the progress indicator doesn’t animate .
The usual reason for a progress indicator to not update is that the programmer is blocking the run loop with a long-running operation, but I’m not doing that: during the period in question, the run loop is totally open, with just a timer waiting to fire. As far as I know, it’s not in some odd mode, either. The app accepts keystrokes and other events during this time without any issues. (The later phase, with a determinate progress indicator filling up, is driven by an asynchronous NSURLConnection, so it’s not blocking either.)
I’ve taken several steps to try to fix this problem:
- I’ve tried setting the Animate binding on the progress indicator to my model’s
waitingForInputproperty, like Is Indeterminate. This causes the animation to update jerkily when change notifications fire onwaitingForInput(waitingForInputhappens to send KVO notifications every time the input delay restarts), but I’m hoping for a much smoother animation than that. - I’ve tried using KVO to observe changes to both
loadingandwaitingForInput. When a change is observed, it calls the progress indicator’s-startAnimation:and-stopAnimation:methods as appropriate. These have no apparent effect. - I’ve tried setting
usesThreadedAnimationon the progress indicator toNO. (A hit on Google suggested this might help with updating problems on layer-backed progress indicators.) This has no apparent effect. I also triedYES, just for kicks, which proved equally futile.
Finally, I’ve also tried turning off layer backing. This does fix the problem when combined with the Animate binding. However, it degrades the performance of other animations unacceptably, so I’d prefer to avoid doing this.
So, any ideas, anyone? I’d really appreciate some help with this problem!
There is no solution that doesn’t require you to either…
a) fumble with the internals of
NSProgressIndicatororb) Roll Your Own™.
So I’d say you should file a bug.
At least on OS X 10.6.5 and above, as soon as you set an indetermined-progress-indicator’s
wantsLayerproperty toYES, the animation stops immediately — you can check that for yourself with a reduced test-app (code below).There was a method called
animate:(deprecated since 10.5) which you could repeatedly call onNSProgressIndicator, which may help you (see Using Indeterminate Progress Indicators).Edit:
Calling
animate:followed by(Edit 2: as Brent noted, this is redundant) from a timer still works. The "may" simply meant that I don’t know if use of deprecated APIs is sanctioned in the App Store or if this matters to you at all.displayIfNeededSample App
Simple Cocoa app with one controller:
In the NIB:
indicatoroutlet of the controller)toggleWantsLayer:as actionAdded by Brent:
I used the information in this answer to write a simple subclass of NSProgressIndicator:
http://www.pastie.org/1465755http://www.pastie.org/1540277Note that in my tests, calling
-animate:worked fine without-displayIfNeeded.Feel free to use it as you see fit. I’d love to hear from you if you use it, though!
Added by Daniel:
A few points about the subclass on pastie:
(Edit 3: fixed in updated snippet).initWithFrame:should call through toinitWithFrame:instead ofinitThe timer need not be retained:(Edit 3: fixed as well).Scheduling an
NSTimercauses the associated runloop toretainand not dispose of it until the timer isinvalidatedThere is a strong candidate for a retain-cycle with the timer: As an(Edit 3: also taken care of).NSTimerretains its target, dealloc will probably never be called if the indicator is released while animated through the timer (I know it’s an edge-case but…)I’m not entirely sure but think that the implementation of(Edit 3: clarified in the updated snippet).awakeFromNibis redundant, since the KVO setup already happened ininitWithFrame:That said, I’d personally prefer not synthesizing
animationTimerand handle the invalidation of the timer in the setter to get rid of the KVO-stuff altogether. (Observingselfis a little outside of my comfort zone.)Added by Anne:
Adding snippet from latest Pastie link for archiving purposes:
ArchProgressIndicator.h
ArchProgressIndicator.m