如何将动画图标添加到 OS X 状态栏?

Posted

技术标签:

【中文标题】如何将动画图标添加到 OS X 状态栏?【英文标题】:How to add animated icon to OS X status bar? 【发布时间】:2011-09-30 12:19:19 【问题描述】:

我想在 Mac OS 状态栏中放置一个图标,作为我的 cocoa 应用程序的一部分。我现在做的是:

NSStatusBar *bar = [NSStatusBar systemStatusBar];

sbItem = [bar statusItemWithLength:NSVariableStatusItemLength];
[sbItem retain];

[sbItem setImage:[NSImage imageNamed:@"Taski_bar_icon.png"]];
[sbItem setHighlightMode:YES];
[sbItem setAction:@selector(stopStart)];

但如果我希望图标具有动画效果(3-4 帧),我该怎么做?

【问题讨论】:

好吧,我想给人一种应用程序正在处理数据的印象——这种情况很少发生,但很高兴知道。还是不应该? 这是一个有效的用途。哎呀,时间机器做到了。 我认为 Dropbox 处理得很好。这很微妙,但它告诉你事情正在更新。 Time Machine 使用来做到这一点。在 macOS 10.15 中不会发生这种情况。 【参考方案1】:

您需要在NSStatusItem 上反复调用-setImage:,每次都传入不同的图像。最简单的方法是使用NSTimer 和一个实例变量来存储动画的当前帧。

类似这样的:

/*

assume these instance variables are defined:

NSInteger currentFrame;
NSTimer* animTimer;

*/

- (void)startAnimating

    currentFrame = 0;
    animTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(updateImage:) userInfo:nil repeats:YES];


- (void)stopAnimating

    [animTimer invalidate];


- (void)updateImage:(NSTimer*)timer

    //get the image for the current frame
    NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:@"image%d",currentFrame]];
    [statusBarItem setImage:image];
    currentFrame++;
    if (currentFrame % 4 == 0) 
        currentFrame = 0;
    

【讨论】:

是否也可以将 NSView 或 NSImageView 放在状态栏项目上?怎么办? 你必须告诉 animTimer 通过 [animTimer fire] 触发还是自动触发? +scheduledTimerWithTimeInterval:... 方法将为您将计时器添加到运行循环(即scheduled 位),因此您无需自己触发计时器。 - (void)updateImage:(NSTimer*)timer add currentFrame++; 谢谢,我添加了计数器递增。【参考方案2】:

我重写了 Rob 的解决方案,以便我可以重复使用它:

我的帧数为 9,所有图像名称的最后一位都是帧号,这样我每次都可以重置图像以动画图标。

//IntervalAnimator.h

    #import <Foundation/Foundation.h>

@protocol IntervalAnimatorDelegate <NSObject>

- (void)onUpdate;

@end


@interface IntervalAnimator : NSObject

    NSInteger numberOfFrames;
    NSInteger currentFrame;
    __unsafe_unretained id <IntervalAnimatorDelegate> delegate;


@property(assign) id <IntervalAnimatorDelegate> delegate;
@property (nonatomic) NSInteger numberOfFrames;
@property (nonatomic) NSInteger currentFrame;

- (void)startAnimating;
- (void)stopAnimating;
@end

#import "IntervalAnimator.h"

@interface IntervalAnimator()

    NSTimer* animTimer;


@end

@implementation IntervalAnimator
@synthesize numberOfFrames;
@synthesize delegate;
@synthesize currentFrame;

- (void)startAnimating

    currentFrame = -1;
    animTimer = [NSTimer scheduledTimerWithTimeInterval:0.50 target:delegate selector:@selector(onUpdate) userInfo:nil repeats:YES];


- (void)stopAnimating

    [animTimer invalidate];


@end

使用方法:

    遵守 StatusMenu 类中的协议

    //create IntervalAnimator object
    
    animator = [[IntervalAnimator alloc] init];
    
    [animator setDelegate:self];
    
    [animator setNumberOfFrames:9];
    
    [animator startAnimating];
    

    实现协议方法

    -(void)onUpdate    
    
        [animator setCurrentFrame:([animator currentFrame] + 1)%[animator numberOfFrames]];
    
        NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:@"icon%ld", (long)[animator currentFrame]]];
    
        [statusItem setImage:image];
    
    
    

【讨论】:

【参考方案3】:

最近在一个简单的项目中不得不做类似的事情,所以我发布了我用 Swift 编写的个人版本:

class StatusBarIconAnimationUtils: NSObject 
    private var currentFrame = 0
    private var animTimer : Timer
    private var statusBarItem: NSStatusItem!
    private var imageNamePattern: String!
    private var imageCount : Int!

    init(statusBarItem: NSStatusItem!, imageNamePattern: String, imageCount: Int) 
        self.animTimer = Timer.init()
        self.statusBarItem = statusBarItem
        self.imageNamePattern = imageNamePattern
        self.imageCount = imageCount
        super.init()
    

    func startAnimating() 
        stopAnimating()
        currentFrame = 0
        animTimer = Timer.scheduledTimer(timeInterval: 5.0 / 30.0, target: self, selector: #selector(self.updateImage(_:)), userInfo: nil, repeats: true)
    

    func stopAnimating() 
        animTimer.invalidate()
        setImage(frameCount: 0)
    

    @objc private func updateImage(_ timer: Timer?) 
        setImage(frameCount: currentFrame)
        currentFrame += 1
        if currentFrame % imageCount == 0 
            currentFrame = 0
        
    

    private func setImage(frameCount: Int) 
        let imagePath = "\(imageNamePattern!)\(frameCount)"
        print("Switching image to: \(imagePath)")
        let image = NSImage(named: NSImage.Name(imagePath))
        image?.isTemplate = true // best for dark mode
        DispatchQueue.main.async 
            self.statusBarItem.button?.image = image
        
    

用法:

private let statusItem = 
    NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

// Assuming we have a set of images with names: statusAnimatedIcon0, ..., statusAnimatedIcon6
private lazy var iconAnimation = 
    StatusBarIconAnimationUtils.init(statusBarItem: statusItem, imageNamePattern: "statusAnimatedIcon", 
    imageCount: 7)

private func startAnimation() 
    iconAnimation.startAnimating()


private func stopAnimating() 
    iconAnimation.stopAnimating()

【讨论】:

以上是关于如何将动画图标添加到 OS X 状态栏?的主要内容,如果未能解决你的问题,请参考以下文章

如何在qt中显示闪烁的[动画]红色/绿色状态图标

如何为 Android 同步状态图标设置动画?

在 OS X 的停靠栏上下文菜单中显示图标?

如何将图标(图像,徽标..)添加到状态栏

在标签栏中添加图像 (.gif)

我们如何在反应原生版本 .61 中将动画添加到底部选项卡图标