MacOS开发——NSMenu应用详解

文章来源汇总:
https://my.oschina.net/u/2340880/blog/1377546
https://blog.csdn.net/lovechris00
https://blog.csdn.net/lovechris00/article/details/78002161
https://blog.csdn.net/henanzhg/article/details/70598302

一、引言
NSMenu在Mac桌面软件开发中往往有3个方面的应用,作为程序的主菜单栏使用,作为视图邮件菜单使用和作为Dock菜单使用。

二、主应用菜单

使用Xcode新建OX S应用时,可以选择使用Storyboard。Storyboard里面会自动创建一个菜单栏,你可以自行在菜单中进行增删改操作,菜单中的Item触发方法也可以直接与AppDelegate进行关联,实现自定义的菜单逻辑,如图:

image

三:Dock菜单

当一款Mac桌面软件运行时,会在Dock栏上显示一个图标,当在此图标上点击右键时,会出现一个Dock菜单,自定义此Dock菜单也十分容易,直接在AppDelegate中重写如下方法即可:

-(NSMenu *)applicationDockMenu:(NSApplication *)sender{
    NSMenu * menu = [[NSMenu alloc]initWithTitle:@"Menu"];
    NSMenuItem * item1 = [[NSMenuItem alloc]initWithTitle:@"菜单1" action:@selector(click) keyEquivalent:@""];
    item1.target = self;
    NSMenuItem * item2 = [[NSMenuItem alloc]initWithTitle:@"菜单2" action:@selector(click) keyEquivalent:@""];
    item2.target = self;
    NSMenuItem * item3 = [[NSMenuItem alloc]initWithTitle:@"菜单3" action:@selector(click) keyEquivalent:@""];
    NSMenu * subMenu = [[NSMenu alloc]initWithTitle:@"subMenu"];
    NSMenuItem * item4 = [[NSMenuItem alloc]initWithTitle:@"菜单4" action:@selector(click) keyEquivalent:@""];
    item4.target = self;
    [subMenu addItem:item4];
    [menu addItem:item1];
    [menu addItem:item2];
    [menu addItem:item3];
    [menu setSubmenu:subMenu forItem:item3];
    return menu;
}

效果如下:

image

在系统代理方法中返回该目录

- (NSMenu *)applicationDockMenu:(NSApplication *)sender
{
    return self.dockMenu;
}

四、视图右键弹出菜单

视图右键弹出菜单是基于NSView视图的,例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMenu * menu = [[NSMenu alloc]initWithTitle:@"Menu"];
    NSMenuItem * item1 = [[NSMenuItem alloc]initWithTitle:@"菜单1" action:@selector(click) keyEquivalent:@""];
    item1.target = self;
    NSMenuItem * item2 = [[NSMenuItem alloc]initWithTitle:@"菜单2" action:@selector(click) keyEquivalent:@""];
    item2.target = self;
    NSMenuItem * item3 = [[NSMenuItem alloc]initWithTitle:@"菜单3" action:@selector(click) keyEquivalent:@""];
    NSMenu * subMenu = [[NSMenu alloc]initWithTitle:@"subMenu"];
    NSMenuItem * item4 = [[NSMenuItem alloc]initWithTitle:@"菜单4" action:@selector(click) keyEquivalent:@""];
    item4.target = self;
    [subMenu addItem:item4];
    [menu addItem:item1];
    [menu addItem:item2];
    [menu addItem:item3];
    [menu setSubmenu:subMenu forItem:item3];
    [self.view setMenu:menu];
}

效果如下:

image

五、NSMenuItem详解

NSMenuItem是菜单中的每一个菜单选项对象,其中常用属性方法如下:

//设置是否启用用户快捷键
+ (void)setUsesUserKeyEquivalents:(BOOL)flag;
//设置用户快捷键启用状态
+ (BOOL)usesUserKeyEquivalents;
//创建一个分割线
+ (NSMenuItem *)separatorItem;
//使用标题,快捷键和方法选择器来对Item进行初始化
- (instancetype)initWithTitle:(NSString *)string action:(nullable SEL)selector keyEquivalent:(NSString *)charCode;
//其所在的菜单对象
@property (nullable, assign) NSMenu *menu;
//其是否有子菜单
@property (readonly) BOOL hasSubmenu;
//子菜单对象
@property (nullable, strong) NSMenu *submenu;
//如果此Item是某个子菜单中的,此属性获取与子菜单关联的父item
@property (nullable, readonly, assign) NSMenuItem *parentItem;
//Item标题
@property (copy) NSString *title;
//富文本标题
@property (nullable, copy) NSAttributedString *attributedTitle;
//是否是分隔线Item
@property (getter=isSeparatorItem, readonly) BOOL separatorItem;
//绑定的快捷键
@property (copy) NSString *keyEquivalent;
//快捷键类型
/*
typedef NS_OPTIONS(NSUInteger, NSEventModifierFlags) {
    NSEventModifierFlagCapsLock           = 1 << 16, // Caps lock键
    NSEventModifierFlagShift              = 1 << 17, // shift键
    NSEventModifierFlagControl            = 1 << 18, // control键
    NSEventModifierFlagOption             = 1 << 19, // option键
    NSEventModifierFlagCommand            = 1 << 20, // command键
    NSEventModifierFlagNumericPad         = 1 << 21, // 小键盘任意键
    NSEventModifierFlagHelp               = 1 << 22, // 帮助键
    NSEventModifierFlagFunction           = 1 << 23, // 任意功能按钮
};
*/
@property NSEventModifierFlags keyEquivalentModifierMask;
//Item图标
@property (nullable, strong) NSImage *image;
//Item状态
@property NSInteger state;
//开启状态下的图标
@property (null_resettable, strong) NSImage *onStateImage;
//关闭状态下的图标
@property (nullable, strong) NSImage *offStateImage;
//混合状态下的图标
@property (null_resettable, strong) NSImage *mixedStateImage;
//是否有效
@property (getter=isEnabled) BOOL enabled;
//是否前置
@property (getter=isAlternate) BOOL alternate;
//Item缩进级别
@property NSInteger indentationLevel;
//设置交互响应者
@property (nullable, weak) id target;
//设置交互相应方法
@property (nullable) SEL action;
//设置tag值
@property NSInteger tag;
//是否高亮
@property (getter=isHighlighted, readonly) BOOL highlighted;
//设置是否隐藏
@property (getter=isHidden) BOOL hidden;
//设置提示文本
@property (nullable, copy) NSString *toolTip;

六、NSMenu详解

//初始化方法
- (instancetype)initWithTitle:(NSString *)title;
//标题
@property (copy) NSString *title;
//在所在的交互点弹出菜单
+ (void)popUpContextMenu:(NSMenu*)menu withEvent:(NSEvent*)event forView:(NSView*)view;
+ (void)popUpContextMenu:(NSMenu*)menu withEvent:(NSEvent*)event forView:(NSView*)view withFont:(nullable NSFont*)font;
- (BOOL)popUpMenuPositioningItem:(nullable NSMenuItem *)item atLocation:(NSPoint)location inView:(nullable NSView *)view NS_AVAILABLE_MAC(10_6);
//设置菜单栏是否可见
+ (void)setMenuBarVisible:(BOOL)visible;
+ (BOOL)menuBarVisible;
//父菜单
@property (nullable, assign) NSMenu *supermenu;
//插入Item
- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index;
- (NSMenuItem *)insertItemWithTitle:(NSString *)string action:(nullable SEL)selector keyEquivalent:(NSString *)charCode atIndex:(NSInteger)index;
//添加Item
- (void)addItem:(NSMenuItem *)newItem;
- (NSMenuItem *)addItemWithTitle:(NSString *)string action:(nullable SEL)selector keyEquivalent:(NSString *)charCode;
//删除某个位置的Item
- (void)removeItemAtIndex:(NSInteger)index;
//删除Item
- (void)removeItem:(NSMenuItem *)item;
//为某个Item设置子菜单
- (void)setSubmenu:(nullable NSMenu *)menu forItem:(NSMenuItem *)item;
//删除所有Item
- (void)removeAllItems;
//Item数组
@property (readonly, copy) NSArray<NSMenuItem *> *itemArray;
//获取Item个数
@property (readonly) NSInteger numberOfItems;
//获取某个位置的Item
- (nullable NSMenuItem *)itemAtIndex:(NSInteger)index;
//获取某个Item的位置
- (NSInteger)indexOfItem:(NSMenuItem *)item;
- (NSInteger)indexOfItemWithTitle:(NSString *)title;
- (NSInteger)indexOfItemWithTag:(NSInteger)tag;
- (NSInteger)indexOfItemWithSubmenu:(nullable NSMenu *)submenu;
- (NSInteger)indexOfItemWithTarget:(nullable id)target andAction:(nullable SEL)actionSelector;
//根据标题获取item
- (nullable NSMenuItem *)itemWithTitle:(NSString *)title;
//根据tag获取Item
- (nullable NSMenuItem *)itemWithTag:(NSInteger)tag;
//刷新菜单
- (void)update;
//获取菜单高度
@property (readonly) CGFloat menuBarHeight;
//取消菜单
- (void)cancelTracking;
- (void)cancelTrackingWithoutAnimation;
//获取高亮的Item
@property (nullable, readonly, strong) NSMenuItem *highlightedItem;
//最小宽度
@property CGFloat minimumWidth;
//尺寸
@property (readonly) NSSize size;
//字体
@property (null_resettable, strong) NSFont *font;

七、状态栏(NSStatusItem)添加菜单
Mac OS X有时会考虑添加NSStatusItem的情况,NSStatusItem并非Mac App必须要使用的,但使用NSStatusItem可以把一些简单的拓展功能放到这里,方便用户的使用。

很多App包括苹果公司本身都使用了NSStatusItem,如上图所示,下面我将分享一些NSStatusItem的使用经验。

使用NSStatusItem应该作为全局变量,而不是局部变量,如果作为局部变量,将没有效果。

.h文件中可以进行如下定义:

@interface AppDelegate : NSObject {
    NSStatusItem *_statusItem;
}

.m文件进行如下实现:

_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
    
    [_statusItem setImage:[NSImage imageNamed:@"image.png"]];
    [_statusItem setToolTip:@"StatusItem"];
    [_statusItem setHighlightMode:YES];

使用位置

NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@"Load_TEXT"];
    
[subMenu addItemWithTitle:@"Load1"action:@selector(load1) keyEquivalent:@"E"];
[subMenu addItemWithTitle:@"Load2"action:@selector(load2) keyEquivalent:@"R"];

statusItem.menu = subMenu;

八、给一个NSView添加右击菜单
这里的快捷键可能无法直接使用。邮件点击该 View,再使用快捷键有效。
一个目录可以添加给多个控件。

- (void)addViewMenu{

    NSMenu *newMenu = [[NSMenu alloc] initWithTitle:@"View 的目录"];
    
    NSMenuItem* newItem = [[NSMenuItem alloc] initWithTitle:@"View 的item" action:@selector(load1) keyEquivalent:@"E"];
    
    [newItem setEnabled:YES];
    [newItem setTarget:self];
    
    [newMenu addItem:newItem];
    
    [self.indicater0 setMenu:newMenu];
    [self.indicater1 setMenu:newMenu];
}

总结:
1.一级目录 和 二级目录 都属于 NSMenu 类。
2.一级目录和二级目录的内容属于 NSMenuItem 类。
3.一个一级目录下,只能有一个二级目录,只能用 setSubmenu 设置一级下的二级目录;
4.二级目录下面可以有多个 item,可以用 addItemWithTitle 和 insertItemWithTitle 来添加。
5.keyEquivalent 是配置的快捷键,如果配置 E,则 command + shif + E 可以调用这个方法。 E 区分大小写,小写无效。
6.insertItem 的时候,需要注意 index 的位置。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354