工具栏和菜单

NSToolbar

NSToolbar工具栏是固定显示在Window窗口顶部一组图标+文字组合的按钮视图。

NSToolbar对象并不继承于NSView,但它有一个私有_toolbarView视图变量,因此我们可以把它理解为一个容器视图,每个Toolbar按钮是一个子元素NSToolbarItem。

NSToolbarItem对象也不是继承自NSView,它也是一个组合对象,包括NSImage,Label等多个子元素。

NSToolba

在xib中设计Toolbar

创建一个工程,从控件工具箱拖动Toolbar到window视图上,toolbar默认包括了Colors,Fonts,Print 3个系统toolbaritem,这个Toolbar会自动调整位置到window的顶部。

ToolbarXib

从左边xib文件导航区选择window->toolbar 点击一个默认生成的item(colors) 进入设计模式,如中间区域所示,浮出一个设计视图。Allow Toolbar Items为所有可用的items,Default Toolbar Items为App运行后实际显示的items。设计阶段可以从右下角工具箱拖放不同的toolbar item到Allow Toolbar Items区域,调整好各个属性参数(下面的NSToolbarItem的属性章节部分详细说明每个参数的意义)。然后从Allow Toolbar Items区域拖到Default Toolbar Items区域。

注意到同一个ToolbarItem 在不同区域显示的文本有所不同,在Allow Toolbar Items区显示为PS ,在Default Toolbar Items为Save,这是由于ToolbarItem设置的Label和Paltte Label属性不同导致。

NSToolbar属性设置

Display:可以设置toolbar的不同显示风格,默认是图标+文字形式,还可以选择只显示图标或只显示文字。

Visible at Launch:是否显示工具栏。

Size:可以选择toolbar视图的高度正常还是小型的。

工具箱中toolbar的类型:

1.Image Toolbar Item:toolbar 中的item项,可以自定义图标和文字。

2.Flexible Space Toolbar Item:放在2个其他Toolbar之间,由系统动态设置宽度分割2个toolbar。

3.Space Toolbar Item:一个标准toolbar item宽度的占位空白区。

4.Separator Toolbar Item:一个标准toolbar item,上面显示一条分割线。

5.Customize Toolbar Item:自定义的toolbar item,用来在toolbar 上放着其他系统控件,比如说搜素框,按钮等。
其他3个为Print,Colors,Fonts系统默认的3个toolbar item。

ToolbarItemType

NSToolbarItem的属性设置

Image Name:图标文件名

Label:App运行后显示的文本

paletteLabel:设计阶段在Allow Toolbar Items区域显示的文本

Tag:用来标示item的唯一数字

Identifier:可标示的字符串

Behavior:Selectable 表示点击后是否有选中的立体效果

ToolbarItemProperties

NSToolbarItem

类属性字段说明

//item所属的toolbar对象
@property (readonly, assign) NSToolbar *toolbar;

//toolbaritem显示的文本
@property (copy) NSString *label;

//在xib的设计模式下toolbaritem显示的文本
@property (copy) NSString *paletteLabel;

//鼠标悬停在toolbaritem上面的时地提示文本
@property (copy) NSString *toolTip;

//tag标识,当多个toolbaritem共用同一个事件响应函数时,通过不同的tag区分
@property NSInteger tag;

//action事件响应的target对象
@property (weak) id target;

@property (nullable) SEL action;

@property (getter=isEnabled) BOOL enabled;

//toolbaritem的图象
@property (strong) NSImage *image;

//当时不使用标准的image/lable模式的toolbaritem时,可以嵌入一个其他的控件,这个view做为它的容器视图。
@property (strong) NSView *view;

//自定义模式下每个item的最小size
@property NSSize minSize;

//自定义模式下每个item的最大size
@property NSSize maxSize;

响应事件

从控件工具箱拖出Image Toolbar Item到xib文件导航区Toolbar上面,点击新建的ToolbarItem,切换到右上边属性面板修改Toolbar默认label为Open,Image Name中选择NSFolder图标。

绑定Open ToolbarItem action事件到openToolbarItemClicked方法,完成事件响应处理。

- (IBAction)openToolbarItemClicked:(id)sender {
    NSToolbarItem *item =  sender;
    NSInteger tag = item.tag;
    //根据每个ToolbarItem的tag做流程处理
    if(tag==1) {
        
    }
    if(tag==2 ) {
        
    }
}

ToolbarItemRun

带选中效果的Open按钮

ToolbarItemRunSelect

创建非标准的Toolbar Item

全部使用标准的图象加文字组合的toolbar item并不总是能满足需要,有时候我们需要放置一些其他控件到toolbar区域,这就需要使用基于视图模式的toolbar item。

在toolbar的设计界面上,可以从控件工具箱拖放搜索框和一个标准的按钮到Allow Toolbar Items区域,
最后从Allow Toolbar Items区域拖到Default Toolbar Items区域完成界面设计。

这些自定义的toolbar item的事件响应方法设置跟它是普通控件时候是一样的。

toolbar-customvie

Validate Toolbar Item

Toolbar上的按钮并不需要总是可用,在一些场景下部分按钮是不满足使用条件,这就需要将这些按钮置为不可用状态,灰掉(不相应鼠标点击事件)。

下面代码可以使FontSetting toolbaritem按钮处于不可点击状态。

-(BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
    if ([theItem.itemIdentifier isEqualToString:@"FontSetting"]) {
        return NO;
    }
    return YES;
}

代码创建NSToolbar

创建NSToolbar对象实例,关联到window对象。实现NSToolbarDelegate协议,返回定义的NSToolbarItem对象。

#define kFontToolbatItemTag    1
#define kSaveToolbatItemTag    2

@interface AppDelegate ()<NSToolbarDelegate>
@property (weak) IBOutlet NSWindow *window;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    [self setUpToolbar];
}

- (void)setUpToolbar {
    NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"AppToolbar"];
    [toolbar setAllowsUserCustomization:NO];
    [toolbar setAutosavesConfiguration:NO];
    [toolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel];
    [toolbar setSizeMode:NSToolbarSizeModeSmall];
    [toolbar setDelegate:self];
    [self.window setToolbar:toolbar];
}

#pragma NSToolbarDelegate 
//所有的item 标识
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
    return @[@"FontSetting",@"Save"];
}

//实际显示的item 标识
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
    return @[@"FontSetting",@"Save"];
}

//根据item 标识 返回每个具体的NSToolbarItem对象实例

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {

    NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
    
    if ([itemIdentifier isEqualToString:@"FontSetting"]) {
        [toolbarItem setLabel:@"Font"];
        [toolbarItem setPaletteLabel:@"Font"];
        [toolbarItem setToolTip:@"Font Setting"];
        [toolbarItem setImage:[NSImage imageNamed:@"FontSetting"]];
        toolbarItem.tag = kFontToolbatItemTag;
        
    }
    else if ([itemIdentifier isEqualToString:@"Save"]) {
        [toolbarItem setLabel:@"Save"];
        [toolbarItem setPaletteLabel:@"Save"];
        [toolbarItem setToolTip:@"Save File"];
        [toolbarItem setImage:[NSImage imageNamed:@"Save"]];
        toolbarItem.tag = kSaveToolbatItemTag;
    }
    else {
        toolbarItem = nil;
    }
    
    [toolbarItem setMinSize:CGSizeMake(25, 25)];
    [toolbarItem setMaxSize:CGSizeMake(100, 100)];
    [toolbarItem setTarget:self];
    [toolbarItem setAction:@selector(toolbarItemClicked:)];
    return toolbarItem;
}

工具栏 Unified Toolbar

Toolbar 和左上角控制窗口关闭、最小化和全屏的三个按钮在同一行。

self.window.titleVisibility =  NSWindowTitleHidden;

UnifiedToolba

NSMenu

菜单是一种多级无限嵌套的UI组件,由菜单NSMenu类和子菜单类NSMenuItem来组合实现菜单功能。

NSMenu可以包含多个NSMenuItem,每个NSMenuItem可以有子菜单对象NSMenu,子菜单对象NSMenu可以继续包含多个NSMenuItem,这样就形成了菜单层级结构。

MenuSubMenu

MenuXib

从左边对象导航树上我们可以看到Main Menu为顶级菜单,它包含了NSMenu,File,Edit等8个子菜单。点击NSMenu这个对象,展开后里面又包含了Menu顶级菜单,再点击Menu顶级菜单,又可以看到它的子菜单项。

UI关键属性

MenuProperties

Title:菜单标题。

Key Equivalent:菜单快捷键。

MenuBox

先来按上图的顺序来认识一下控件工具箱中关键的几个菜单控件吧:

1.Menu:整个应用的菜单对象。可以删除MainMenu.xib中的Main Menu,全部来自定义所有的菜单。一般不推荐这样做,因为应用的关于,帮助,退出这些菜单是必须有的。如果需要自己的菜单直接在系统生产的默认菜单里面修改增加自己的就可以了。

2.Menu Item:这个是子菜单项

3.Submenu Menu Item:带有子菜单的菜单项,创建多级菜单时需要使用。

4.Separator Menu Item:分割线,用来对菜单分组。

后面剩下的菜单控件分别代表系统默认生成的所有菜单的顶级菜单项,也就是说你把默认的顶级任何一个菜单删除,都可以从这里拖一个出去重现创建。

增加顶级菜单

拖动Submenu Menu Item 控件到左侧MainMenu.xib 文件导航区中的Main Menu上即可完成。菜单位置可以可以拖动调整。

增加子菜单项

MainMenu.xib 文件导航区点击选择要增加子菜单的菜单项,从Xcode控件工具箱中拖动Menu Item。

同里可以拖动Submenu Menu Item到某个菜单项完成增加子菜单的2级菜单。

菜单响应事件

Connection面板中Sent Actions中连接selector到自定义的菜单方法即可。

弹出式菜单

从控件工具箱拖动Menu类型的一个菜单对象到xib文件中,修改名称为MyMenu。绑定菜单对象到 IBOutlet NSMenu *myMenu 变量。

增加PopMenu按钮,绑定popButtonClicked事件。

PopMenu

调用popUpMenuPositioningItem方法即可实现菜单弹出显示。

- (IBAction)popButtonClicked:(id)sender {
    NSButton *button = (NSButton *)sender;
    NSPoint point = button.frame.origin;
    point.x += button.frame.size.width;
    point.y = point.y ;
    [self.myMenu popUpMenuPositioningItem:nil atLocation:point inView:self.window.contentView];
}

设置上下文菜单

任何view可以设置一个menu,鼠标右键后会弹出上下文菜单

view.contextView.menu = self.customMenu;

customMenu为在xib界面上拖放的NSMenu控件的绑定变量或者通过代码创建的一个菜单对象实例。

Dock menu

每个应用在Dock面板上icon鼠标右击会出现一个菜单,里面是系统默认的菜单。每个应用可以增加自定义菜单项到自己的Dock菜单上。

1.window的xib界面上拖放一个NSMenu,绑定到menu变量,完成menuitem的名称定义和方法实现。

2.AppDelegate中实现下面协议方法

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

在Dock面板上右键菜单,弹出菜单会就会显示自定义的菜单。

DockMenu

代码创建菜单

1.创建菜单对象

NSMenu *customMenu = [[NSMenu alloc]init];

2.创建子菜单

NSMenuItem *openMenuItem = [[NSMenuItem alloc]initWithTitle:@"Open" action:@selector(menuClicked:) keyEquivalent:@"O"];
[openMenuItem setKeyEquivalentModifierMask:NSShiftKeyMask];

菜单的快捷键为 shift + O的组合。

3.子菜单里面增加2级菜单

NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc]initWithTitle:@"Open Recent..." action:nil keyEquivalent:@""];
    
NSMenu *recentMenu = [[NSMenu alloc]init];
//设置子菜单的2级菜单
[openRecentMenuItem setSubmenu:recentMenu];
   
NSMenuItem *file1MenuItem = [[NSMenuItem alloc]initWithTitle:@"File1" action:@selector(menuClicked:) keyEquivalent:@""];
 
[recentMenu addItem:file1MenuItem];

4.增加子菜单到上一级

[customMenu addItem:openMenuItem];
[customMenu addItem:openRecentMenuItem];

获取App应用默认的菜单

NSMenu *menu = [NSApp menu];
NSArray *subitems = [menu itemArray];
    
for(NSMenuItem *item in subitems) {
        NSString *title = item.title;
        NSLog(@"title %@",title);
        NSMenu *submenu = item.menu;
        NSArray *subsubitems = [submenu itemArray];
        for(NSMenuItem *subsubitem in subsubitems) {
            NSLog(@"subsubitem %@",subsubitem.title);
     }
 }
 

上面代码先获取应用的菜单对象,然后获取到所有1级菜单项,遍历打印输出菜单标题。逐级获取到1级菜单的2级子菜单,打印输出标题。

如果要动态的增加系统菜单项,可以使用上述方法定位到要增加的父菜单,然后把自己的菜单项增加进去,使用下面的NSMenu方法可以增进子菜单到指定的index位置。

-(void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index;

菜单有效性验证validate

根据场景来决定菜单项是否可用。

对菜单项功能根据应用当前的状态判断是否可用。返回YES表示菜单项可用,返回菜单项不可用。

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
    if([menuItem action] == @selector(closeTab:)) {
        if(![tabBar canCloseOnlyTab] && ([tabView numberOfTabViewItems] <= 1)) {
            return NO;
        }
    }
    return YES;
}

NSStatusBar

用来在系统右上角状态栏区域增加快捷入口,可以是文字或图标的形式,用来开发menubar 风格的应用。

StatusBar

NSStatusBar对象关系

NSStatusBar为OSX 系统中全局单例对象,NSStatusBar管理所有的menubar item,每个item是NSStatusItem对象。

通过NSStatusBar来创建新的NSStatusItem 对象。NSStatusItem对象有2个关键属性,NSStatusBarButton和NSMenu。NSStatusBarButton继承自NSButton,可以通过action-target模式设置响应事件。

NSStatusBarArchitecture

NSStatusBar 使用

从上面的对象关系图最右边分支看出,NSStatusItem有2种方式的使用,基于按钮方式和下拉菜单方式2种。

图标按钮方式的使用

NSStatusBarButton由NSStatusItem自动创建,只需要像普通的button一样,设置它的image,title,action,target属性就可以了。

创建工程NSStatusBarDemo,定义属性变量NSStatusItem *item,用来保存创建的item。

AppDelegate启动后先调用NSStatusBar的systemStatusBar方法获取系统的NSStatusBar对象实例,
在通过NSStatusBar的statusItemWithLength方法创建了固定宽度的NSStatusItem实例。对NSStatusItem进行属性设置,最后保存item到属性变量。这一步是必须的,否则由于OC的ARC内容管理机制,对象马上释放掉了,系统的NSStatusBar上不会出现这个item。

代码中给按钮绑定了响应事件itemAction:,在这个方法中可以灵活实现自己的功能,比如弹出一个面板窗口或者做为应用本身的快捷入口,每次点击NSStatusBar按钮来激活应用(将处于非活动状态的应用窗口呼出到最前面)。

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property (strong,nonatomic) NSStatusItem *item;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    //获取系统单例NSStatusBar对象
    NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
    //创建固定宽度的NSStatusItem
    NSStatusItem *item = [statusBar statusItemWithLength:NSSquareStatusItemLength];
    [item.button setTarget:self];
    [item.button setAction:@selector(itemAction:)];
    item.button.image = [NSImage imageNamed:@"blue@2x.png"];
    
    //保存到属性变量
    self.item = item;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
    NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
    //删除item
    [statusBar removeStatusItem:self.item];
}

- (IBAction)itemAction:(id)sender {
    NSLog(@"itemAction");
    //激活应用到前台(如果应用窗口处于非活动状态)
    [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
}

@end

触发下拉菜单

创建工程NSStatusBarDemo,从控件工具箱拖放NSMenu到xib界面,绑定NSMenu到IBOutlet变量shareMenu。

AppDelegate启动后通过NSStatusBar创建NSStatusItem,设置item的menu为shareMenu。

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSMenu *shareMenu;
@property (strong,nonatomic) NSStatusItem *item;
@end

@implementation AppDelegate

-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    //获取系统单例NSStatusBar对象
    NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
    //创建固定宽度的NSStatusItem
    NSStatusItem *item = [statusBar statusItemWithLength:NSSquareStatusItemLength];
    item.button.image = [NSImage imageNamed:@"blue@2x.png"];
    //设置下拉菜单
    item.menu = self.shareMenu;
    //保存到属性变量
    self.item = item;
}

-(void)applicationWillTerminate:(NSNotification *)aNotification {
    NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
    //删除item
    [statusBar removeStatusItem:self.item];
}

-(IBAction)shareAction:(id)sender {
    NSLog(@"shareAction");
}
@end

如果通过action,target定义了Button点击响应事件,同时设置了NSMenu,则点击NSStatusBar按钮时优先显示下拉菜单。

增加Popover弹出视图

通过按钮方式也可以弹出Popover视图来实现一个简单的应用。

1.创建ViewController,定义界面视图

工程中增加一个名叫AppViewController的视图控制器类。

AppViewControlle

设计AppViewController界面,在xib界面增加一个文本标签和图象控件。

AppViewControllerXib

2.增加NSPopover,用来管理ViewController

定义NSPopover类型的属性变量popover,同时增加一个isShow布尔变量来表示popover是否已经显示

@interface AppDelegate ()
....

@property (strong) NSPopover *popover;
@property(nonatomic)BOOL  isShow;
@end

配置NSPopover,设置它的contentViewController为AppViewController

-(void) setUpPopover {
    self.popover = [[NSPopover alloc] init];
    self.popover.contentViewController = [[AppViewController alloc] init];
    self.popover.behavior = NSPopoverBehaviorApplicationDefined;
}

3.实现按钮事件

当self.isShow为NO时显示popover,否则关闭popover.

-(IBAction)itemAction:(id)sender {
    if (!self.isShow) {
        [self.popover showRelativeToRect:NSZeroRect ofView:[self item].button preferredEdge:NSRectEdgeMinY];
    } else {
        [self.popover close];
    }
    self.isShow = ~self.isShow;
}

StatusPopoverVie