Animate OS X status bar icon (NSStatusItem)

 Why?

With OS X 10.10 (aka Yosemite) Apple decided to remove animations from menu bar extras icons. Maybe not entirely (e.g. Wi-Fi icon is still animating when connecting to network, on the other hand with all these issues with Wi-Fi on Yosemite… never mind), but my favorite Time Machine animation is gone. Yet, in some cases it’s useful to have short animation to indicate something to user. My app, NepTunes is scrobbling tracks and would be nice, to inform user that everything is fine in unobtrusive way.

 How?

This tutorial is for all folks, who want to support OS X Mavericks. For OS X Yosemite and newer, NSStatusItem has a button property, which we can use with Core Animation.
For NepTunes I wanted animation with series of images using NSStatusItem’s image property.

But unfortunately, NSImage isn’t as powerful as iOS UIImage.

Here we go!

static NSUInteger const kFPS = 30;  
static NSUInteger const kNumberOfFrames = 10;

-(void)animationStepForward:(BOOL)forward
{
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 1.0 / kFPS * NSEC_PER_SEC);
    dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        if (forward) {
            self.animationCurrentStep++;
            if (self.animationCurrentStep <= kNumberOfFrames) {
                [self animationStepForward:YES];
            } else {
                self.animationCurrentStep = 0;
            }
        } else {
            self.animationCurrentStep--;
            if (self.animationCurrentStep > 0) {
                [self animationStepForward:NO];
            } else {
                self.animationCurrentStep = 0;
            }
        }
             dispatch_async(dispatch_get_main_queue(), ^{
            if (self.animationCurrentStep != 0) {
                self.statusItem.image = [self imageForStep:self.animationCurrentStep];
            } else {
                if (forward) {                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        [self backwardAnimation];
                    });
                } else {
                    [self setOriginalIcon];
                }
            }
        });
    });
}  

First we have two constants for keeping number of frames (and number of images) and speed of our animation.

In animationStepForward method we calculate delay between frames, in our case it will be 1/30 of the second.
Next, using Grand Central Dispatch dispatch_after function, we’re delaying execution of block.

In block, because our method support animation forwards and backwards, we’re incrementing or decrementing counter and we call ourself recursively. After that, using dispatch_async function we’re setting image of our NSStatusItem with method imageForStep: . If we’re on the end of animation we call handler (to stop on the last frame for one second in this case).

This is imageForStep::

-(NSImage *)imageForStep:(NSUInteger)step
{
    NSImage *image;
    if (step != 0) {
        image = [NSImage imageNamed:[NSString stringWithFormat:@"statusIcon%lu", (unsigned long)step]];
    } else image = [NSImage imageNamed:@"statusIcon"];
    [image setTemplate:YES];
    return image;
}

[image setTemplate:YES] is for supporting dark theme on OS X Yosemite and later and statusIcon is the common name of files used in animation (statusIcon1.pdf, statusIcon2.pdf, statusIcon3.pdf etc.).

 
6
Kudos
 
6
Kudos

Now read this

NepTunes 1.2 Changelog (Full version)

What’s NepTunes? Simple to use, yet powerful iTunes controller for Mac with Last.fm scrobbler that is light, super reliable and fully works with Apple Music streaming and radio and can help you with discovering new music. Only $3.99. You... Continue →

Subscribe to micropixels blog

Don’t worry; we hate spam with a passion.
You can unsubscribe with one click.

JyklJUDBWFbcD4hJX6