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.).

 
8
Kudos
 
8
Kudos

Now read this

NepTunes 1.1 Changelog

1.1 Changelog [NEW] Last.fm cover (if it’s available) in notification [NEW] Apple Music and iTunes integration! You can check option to integrate Last.fm results with Apple Music. Love track on both Apple Music (when track is in library... Continue →