Lion 上的 Popover 状态栏小程序(转)

(原文:http://qusic.me/post/28393310143/menulet-with-popover)

状态栏小程序(Menulet)也算一个标准的 Cocoa 应用程序,不过特殊的是它:

  • 只在状态栏显示一个图标(或文字等),不带有窗口、主菜单
  • 运行时不会有图标出现在在 Dock 栏
  • 不能获得焦点成为主窗口

向 Menulet 添加弹出下拉菜单是很简单的。所以下面要讨论的是一个弹出 Popover 的 Menulet(类似 Mac 版 QQ 的效果,如下图)。

NSPopover

官方文档传送门
需要注意的是 Popover 只在 Mac OS X v10.7(Lion) 及以上版本可用。

开始!

一、创建项目

  • 使用 Cocoa Application 模板即可。

二、创建 PopoverController 类

  1. 使用新文件向导创建一个 NSViewController 的子类,选中 With XIB for user interface。
  2. 用 Interface Builder 在 Xcode 生成的 XIB 文档中添加一个 UIView 作为 Popover 的内容。
  3. 向上面的 UIView 中再添加各种内容。这里作为例子我就只写个 Hello World 了。

三、创建 Menulet 类和 MenuletController 类

  • Menulet 类必须是 NSView 的子类,因为我们要将它将作为我们创建的系统状态栏上的项目(NSStatusItem)的视图,用来显示图标,同时接受用户的单击事件。
  • MenuletController 类用来记录 Menulet 的活动状态,并在恰当的时候通过 Popover 来显示或隐藏我们之前创建的 NSView。
//MenuletController.h

@protocol MenuletDelegate 

- (BOOL)isActive; //是否已被单击
- (void)clicked; //被单击时调用的方法

@end

@interface MenuletController : NSObject 

@property (assign, nonatomic, getter = isActive) BOOL active;
@property (strong, nonatomic) NSPopover *popover;

@end
//MenuletController.m

@implementation MenuletController

@synthesize active;
@synthesize popover;

- (void)clicked
{
    self.active = !active; //每次被单击时改变 active 的值
    if (active) { //显示 popover
        [self setupPopover];
        AppDelegate *appDelegate = [NSApp delegate];
        [popover showRelativeToRect:[appDelegate.menulet frame] ofView:appDelegate.menulet preferredEdge:CGRectMinYEdge];
    } else { //关闭 popover
        [popover performClose:self];
    }
}

- (void)setupPopover
{
    if (!popover) {
        popover = [[NSPopover alloc] init];
        popover.contentViewController = [[PopoverController alloc] init];
    }
}

@end
//Menulet.h

@interface Menulet : NSView

@property (weak, nonatomic) id delegate;

@end
//Menulet.m

@implementation Menulet

@synthesize delegate;

- (void)setDelegate:(id)newDelegate
{
    [(NSObject *)newDelegate addObserver:self forKeyPath:@"active" options:NSKeyValueObservingOptionNew context:nil]; //观察 active 的值的改变
    delegate = newDelegate;
}

- (void)mouseDown:(NSEvent *)event
{
    [delegate clicked];
}

- (void)drawRect:(NSRect)rect //绘制图标的代码
{
    rect = CGRectInset(rect, 2, 2);
    if ([delegate isActive]) {
        [[NSColor highlightColor] set];
    } else {
        [[NSColor textColor] set];
    }
    NSRectFill(rect);
    [[NSImage imageNamed:@"Menulet"] drawInRect:rect fromRect:NSZeroRect operation:NSCompositeDestinationIn fraction:1.0];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    [self setNeedsDisplay:YES]; //active 值改变时重新绘制图标
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent //避免焦点不在状态栏时需要单击两次才能点击到图标的问题
{
    return YES;
}

@end

四、把 Menulet 添加到系统状态栏

//AppDelegate.h

@interface AppDelegate : NSObject 

@property (strong, nonatomic) NSStatusItem *statusItem;
@property (strong, nonatomic) Menulet *menulet;
@property (strong, nonatomic) MenuletController *controller;

@end
//AppDelegate.m

@implementation AppDelegate

@synthesize statusItem;
@synthesize menulet;
@synthesize controller;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    CGFloat thickness = [[NSStatusBar systemStatusBar] thickness];
    statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:thickness]; //正方形
    menulet = [[Menulet alloc]initWithFrame:(NSRect){.size={thickness, thickness}}];
    controller = [[MenuletController alloc] init];
    menulet.delegate = controller;
    [statusItem setView:menulet];
}

@end
  • 为了避免运行程序时出现主菜单以及 Dock 图标,我门还需要在 Info.plist 中添加一个键 Application is agent (UIElement),值为 YES。

完成!

现在可以单击运行按钮看效果了。如下图。

Xcode Project Download

posted @ 2013-04-14 15:02  bxddno1  阅读(733)  评论(0)    收藏  举报