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 类
- 使用新文件向导创建一个 NSViewController 的子类,选中 With XIB for user interface。
- 用 Interface Builder 在 Xcode 生成的 XIB 文档中添加一个 UIView 作为 Popover 的内容。
- 向上面的 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。
完成!
现在可以单击运行按钮看效果了。如下图。



浙公网安备 33010602011771号