Mac OS的选项-NSPopUpButton(多选一)/NSMenu(菜单)



Mac OS上,坐标系统原点位于左下角!


下面使用的素材图片:

使用的图片素材



Mac端除了NSButton按钮作为选项外,还可以使用NSPopUpButton(多选一)和NSMenu(菜单)


NSPopUpButton

为NSPopUpButton设置背景色——为红色

let speedPopBtn = NSPopUpButton(frame: NSMakeRect(100.0, 100.0, 100.0, 30.0))
self.view .addSubview(speedPopBtn)
//设置背景色(一般不会去设置)
speedPopBtn.wantsLayer = true
speedPopBtn.layer?.backgroundColor = NSColor.red.cgColor

展示效果:NSPopUpButton背景色一般情况不会去设置


初始化NSPopUpButton实例,添加在视图控制器的view上!并设置其数组(所拥有的项)!

let speedPopBtn = NSPopUpButton(frame: NSMakeRect(100.0, 100.0, 100.0, 30.0))
self.view .addSubview(speedPopBtn)
//风格设置是否为下拉菜单(默认为false!设置为ture后需要需要在对应selector里设置选中项对应标题)
speedPopBtn.pullsDown = false//设置为false,选择后标题才改变!
//设置数组(所拥有的项)
let typeArr = ["常速", "2倍速", "4倍速", "8倍速", "16倍速"]
speedPopBtn .addItems(withTitles: typeArr)

效果:有"常速", "2倍速", "4倍速", "8倍速", "16倍速"这些选项,可以进行选择!


移除某一项:open func removeItem(at index: Int)

//移除某一项
speedPopBtn .removeItem(at: (typeArr.count - 1))//移除最后一项

效果:少了最后一项"16倍速"


在末尾添加新的项:open func addItem(withTitle title: String)

//在末尾添加新的项
speedPopBtn .addItem(withTitle: "16倍速")
speedPopBtn .addItem(withTitle: "32倍速")

效果:又多了"16倍速"、"32倍速"这2项!


指定位置插入某一项:open func insertItem(withTitle title: String, at index: Int)

//指定位置插入某一项
speedPopBtn .insertItem(withTitle: "1/2倍速", at: 0)

效果:当前第一项变为"1/2倍速"!


打印NSPopUpButton实例的数组-当前所有项:

//打印PopBtn的数组-当前所有项
print(speedPopBtn.itemArray)

打印如下:
[<NSMenuItem: 0x600002903720 1/2倍速>, <NSMenuItem: 0x600002911180 常速>, <NSMenuItem: 0x6000029034f0 2倍速>, <NSMenuItem: 0x600002903480 4倍速>, <NSMenuItem: 0x6000029035d0 8倍速>, <NSMenuItem: 0x600002903640 16倍速>, <NSMenuItem: 0x6000029036b0 32倍速>]


设置NSPopUpButton的选中项:open func selectItem(at index: Int)

//设置 默认选中项——首项:"1/2倍速"
speedPopBtn .selectItem(at: 0)

设置默认选中项的前后效果对比:


设置弹出菜单的位置:open var preferredEdge: NSRectEdge属性——对应枚举的各项(minX/minY/maxX/maxY)

 public enum NSRectEdge : UInt {


    case minX = 0

    case minY = 1

    case maxX = 2

    case maxY = 3
}

对应情况的效果如下图:


添加选择时的响应事件:设置targetaction

//添加选择时的响应事件
speedPopBtn.target = self; speedPopBtn.action = #selector(handleSelectPopBtn)

选择时的响应事件:

@objc func handleSelectPopBtn(popBtn : NSPopUpButton) {//NSPopUpButton选择
    print("popBtn.indexOfSelectedItem:",popBtn.indexOfSelectedItem)
    
}

效果:



可以为NSPopUpButton设置新的自定义弹出菜单:NSMenu实例
[A].menu属性(open var menu: NSMenu?)的控件

//设置新的自定义弹出菜单
let menu = NSMenu(title: "Menu")
menu .insertItem(withTitle: "one", action: nil, keyEquivalent: "", at: 0)   //指定位置插入
menu .addItem(withTitle: "two", action: nil, keyEquivalent: "")             //末尾添加
menu .addItem(withTitle: "three", action: #selector(clickThree), keyEquivalent: "")//末尾添加
speedPopBtn.menu = menu;

选择自定义弹出菜单中某项时的响应事件:

//注意:响应了设置自定义弹出菜单NSMenu的项NSMenuItem对应方法,就不会响应NSPopUpButton选择的对应方法
@objc func clickThree(item: NSMenuItem) {
    print("clickThree!", "item.title is",item.title)
    
}

UI效果:

注意响应了设置自定义弹出菜单NSMenu的NSMenuItem项对应方法,就响应NSPopUpButton选择的对应方法

操作效果如下GIF:当选择了"three"项时,该项只响应其自己的#selector(clickThree)方法!所以只打印了“clickThree! item.title is three”!




NSMenu

NSMenu除了用在上面为NSPopUpButton控件设置新的自定义弹出菜单——[A].menu属性(open var menu: NSMenu?)的控件
还可以:[B].添加NSApp对应顶部菜单栏菜单(mainMenu)、[C].添加顶部状态栏菜单(NSStatusBar)、[D].添加Dock菜单[E].用来作为NSEvent事件菜单



[B].添加NSApp对应顶部菜单栏菜单(mainMenu)

//获取NSApp的主目录
let mainMenu = NSApp.mainMenu
let itemRoot = NSMenuItem(title: "根菜单项", action: #selector(clickMenu), keyEquivalent: "")
itemRoot.title = "Load_TEXT"//设置根目录项的title文字无效
let rootMenu = NSMenu(title: "RootMenu-根目录")//文字会以根目录的title来显示
//必须先设置根目录(第一层)——rootMenu、根目录项——itemRoot
mainMenu? .setSubmenu(rootMenu, for: itemRoot)//添加一级目录(根目录),可往下一直添加N级目录
//在末尾添加"根菜单项"
//mainMenu? .addItem(itemRoot)
//在指定位置添加"根菜单项"
mainMenu? .insertItem(itemRoot, at: (0 + 2))

步骤:1.先通过NSApp.mainMenu获取到NSApp主目录,2.必须再通过.setSubmenu设置上根目录(第一层):NSMenu实例、根目录项:NSMenuItem实例,3.再通过.addItem.insertItem插入到NSApp的主目录——此时在屏幕顶部App的菜单栏菜单中就有了添加的新项("RootMenu-根目录")!

效果:点击选择时不会弹出根目录的菜单!(因为未对根目录 添加下一级菜单!)

选择时不会弹出根目录的菜单!


为根目录添加一级、二级(……)目录及相应项:

//添加一级目录项
let item1 = NSMenuItem(title: "菜单1", action: #selector(clickMenu), keyEquivalent: "")
item1.target = self;
let item2 = NSMenuItem(title: "菜单2", action: #selector(clickMenu), keyEquivalent: "")
item2.target = self;
let item3 = NSMenuItem(title: "菜单3", action: #selector(clickMenu), keyEquivalent: "")
item3.target = self;
rootMenu .addItem(item1)
rootMenu .addItem(item2)
rootMenu .addItem(item3)

let subMenu = NSMenu(title: "Menu3-subMenu")
rootMenu .setSubmenu(subMenu, for: item3)//添加二级目录——subMenu
let item3_1 = NSMenuItem(title: "菜单3-1", action: #selector(clickMenu), keyEquivalent: "")
item3_1.target = self
let item3_2 = NSMenuItem(title: "菜单3-2", action: #selector(clickMenu), keyEquivalent: "")
item3_2.target = self
//添加二级目录项
subMenu .addItem(item3_1)
subMenu .addItem(item3_2)

选择时响应的方法:

@objc func clickMenu(menuItem: NSMenuItem) {
    print("App clickMenu", menuItem.title)
    
}

效果:

选择各项时的打印:

App clickMenu 菜单1
App clickMenu 菜单2
App clickMenu 菜单3
App clickMenu 菜单3-1
App clickMenu 菜单3-2



[C].添加顶部状态栏菜单(NSStatusBar)

//为状态栏定义并添加2个项(App是开启状态就会在状态栏上展示出来)
let stsItem = NSStatusBar .system .statusItem(withLength: 120)//自定义长度
let img = NSImage(named: "usr_item_email")
if #available(macOS 10.14, *) {
    stsItem.button?.image = img
    //stsItem.button?.alternateImage = img//选中时的图片
    stsItem.button?.imageScaling = NSImageScaling.scaleProportionallyUpOrDown
} else {
    stsItem.image = img
    //stsItem.alternateImage = img//选中时的图片
}
stsItem.isVisible = true//是否在状态栏中可见,默认为true

let stsItem2 = NSStatusBar.system .statusItem(withLength: NSStatusItem.squareLength)//标准的长度
let img2 = NSImage(named: "usr_Set_Icon")
if #available(macOS 10.14, *) {
    stsItem2.button?.image = img2
    //stsItem.button?.alternateImage = img2//选中时的图片
    stsItem2.button?.imageScaling = NSImageScaling.scaleProportionallyUpOrDown
} else {
    stsItem2.image = img2
    //stsItem2.alternateImage = img2//选中时的图片
}
stsItem2.isVisible = true

//在最后:必须用以下任一方法,加入任何一项NSStatusItem到状态栏(必须调用,才会展示新添加的2个项!)
//NSStatusBar .insertValue(stsItem2, inPropertyWithKey: "statusMenu")
NSStatusBar .system .insertValue(stsItem2, inPropertyWithKey: "statusMenu")

注意:在最后必须用NSStatusBar的.insertValue.system .insertValue任一方法,加入上面任何一项NSStatusItem到状态栏——才会展示新添加的2个项!

效果:为状态栏添加了2个项(App开启状态时,就会在状态栏上展示出来)!其中stsItem长度为120、stsItem2为标准长度

为stsItem2添加NSMenu菜单:

let statusMenu = NSMenu(title: "statusMenu")//添加NSMenu菜单
statusMenu.minimumWidth = 200
stsItem2.menu = statusMenu
statusMenu .insertItem(withTitle: "客户头像", action: #selector(statusBarItemClickMenu), keyEquivalent: "", at: 0)
statusMenu .insertItem(withTitle: "客户信息", action: #selector(statusBarItemClickMenu), keyEquivalent: "", at: 1)
statusMenu .insertItem(withTitle: "客户权限", action: #selector(statusBarItemClickMenu), keyEquivalent: "", at: 2)

let item3 = statusMenu.items[0+2] as NSMenuItem  //类型转换
let subMenu = NSMenu(title: "Menu3-subMenu")
statusMenu .setSubmenu(subMenu, for: item3)//添加二级目录——subMenu
let item3_1 = NSMenuItem(title: "菜单3-1", action: #selector(statusBarItemClickMenu), keyEquivalent: "")
item3_1.target = self
let item3_2 = NSMenuItem(title: "菜单3-2", action: #selector(statusBarItemClickMenu), keyEquivalent: "")
item3_2.target = self
//添加二级目录项
subMenu .addItem(item3_1)
subMenu .addItem(item3_2)

注意:任何状态栏中菜单操作,都必须在NSStatusBar的.insertValue.system .insertValue方法之前调用!

选择时响应的方法:

@objc func statusBarItemClickMenu(menuItem: NSMenuItem) {
    print("statusBar Item clickMenu", menuItem.title)
    
}

效果:

选择各项时的打印:

statusBar Item clickMenu 客户头像
statusBar Item clickMenu 客户信息
statusBar Item clickMenu 客户权限
statusBar Item clickMenu 菜单3-1
statusBar Item clickMenu 菜单3-2

Tips:使用自定义视图
之前系统方法被弃用了,选择直接添加到NSMenuItem项的.button

//自定义视图
let sts_w = stsItem2.button?.frame.size.width
let sts_h = stsItem2.button?.frame.size.height
let customerV = NSView(frame: NSMakeRect(0, 0, sts_w!, sts_h!))
customerV.wantsLayer = true
customerV.layer?.backgroundColor = NSColor.red.cgColor
//stsItem2.view = customerV//存在问题:A.选择stsItem2项无反应、B.警告——'view' was deprecated in macOS 10.14: Use the standard button property instead
stsItem2.button?.addSubview(customerV)

效果:

注意:此操作也必须在NSStatusBar的.insertValue.system .insertValue方法之前调用!


最终的代码:调用NSStatusBar的.insertValue.system .insertValue方法,必须放在最后面!

最终的代码



[D].添加Dock菜单

核心方法:NSApplication的“- (nullable NSMenu *)applicationDockMenu:(NSApplication *)sender;”方法!Swift中长这样—func applicationDockMenu(_ sender: NSApplication) -> NSMenu?

不使用:a.不将“其公开并书写”、b.将返回设置为nil(如下)。

func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
    return nil
}

执行时的效果:

当在“func applicationDockMenu(_ sender: NSApplication) -> NSMenu?”方法中:添加一些‘dockMenu’项!

let dockMenu = NSMenu(title: "dockMenu")
let item1 = NSMenuItem(title: "菜单1", action: #selector(clickDockMenu), keyEquivalent: "")
item1.target = self;
let item2 = NSMenuItem(title: "菜单2", action: #selector(clickDockMenu), keyEquivalent: "")
item2.target = self;
let item3 = NSMenuItem(title: "菜单3", action: #selector(clickDockMenu), keyEquivalent: "")
item3.target = self;
dockMenu .addItem(item1)
dockMenu .addItem(item2)
dockMenu .addItem(item3)
let subMenu = NSMenu(title: "Menu3-subMenu")
let item3_1 = NSMenuItem(title: "菜单3-1", action: #selector(clickDockMenu), keyEquivalent: "")
item3_1.target = self
let item3_2 = NSMenuItem(title: "菜单3-2", action: #selector(clickDockMenu), keyEquivalent: "")
item3_2.target = self
subMenu .addItem(item3_1)
subMenu .addItem(item3_2)

dockMenu .setSubmenu(subMenu, for: item3)//添加二级目录,可往下一直添加N级目录  //设置后点击"菜单3"无效,但可点击"菜单3-1"、"菜单3-2"

展示效果:

所有 新增的dockMenu项

选择时的打印:

dockMenu clickMenu 菜单1
dockMenu clickMenu 菜单2
dockMenu clickMenu 菜单3-1
dockMenu clickMenu 菜单3-2

选择时效果:"菜单3"无效,但可点击"菜单3-1"、"菜单3-2"

对于Dock菜单当为某一项添加了子菜单后,选择该项不响应对应事件,但选择子项会响应对应事件



[E].用来作为NSEvent事件菜单

当有NSEvent事件时,可使用如下方法传入NSEvent事件后创建一个弹出菜单!

open class func popUpContextMenu(_ menu: NSMenu, with event: NSEvent, for view: NSView)

open class func popUpContextMenu(_ menu: NSMenu, with event: NSEvent, for view: NSView, with font: NSFont?)

而获取NSEvent事件,我用的是鼠标右键点下例子,需重写NSApplication的- (void)rightMouseDown:(NSEvent *)event;方法
override func rightMouseDown(with event: NSEvent) {}中:

let menuForUsr = NSMenu(title: "编辑用户")
let item1 = NSMenuItem(title: "设置头像", action: nil, keyEquivalent: "")
let item2 = NSMenuItem(title: "基本信息设置", action: nil, keyEquivalent: "")
let item3 = NSMenuItem(title: "权限等级申请", action: nil, keyEquivalent: "")
//对item3项,设置子菜单及相应选项
let permissionMenu = NSMenu(title: "权限等级申请")
let item3_1 = NSMenuItem(title: "支付权限", action: nil, keyEquivalent: "")
let item3_2 = NSMenuItem(title: "对话权限", action: nil, keyEquivalent: "")
permissionMenu .addItem(item3_1)
permissionMenu .addItem(item3_2)
item3.submenu = permissionMenu

let item4 = NSMenuItem(title: "展示在线状态", action: nil, keyEquivalent: "")
//当.onStateImage、.offStateImage尺寸超出后将把字体覆盖掉!而.image尺寸超出后会按照图片大小的尺寸展示出来!
item4.onStateImage = NSImage(named: "usr_item_sel")
item4.offStateImage = NSImage(named: "usr_item_nor")
let item5 = NSMenuItem(title: "客户邮件消息", action: nil, keyEquivalent: "")
item5.image = NSImage(named: "usr_item_email")//.image与标题一起

menuForUsr .insertItem(item1, at: 0)    //指定位置插入某一项
menuForUsr .addItem(item2)  //末尾添加某一项
menuForUsr .insertItem(item3, at: (0+2))
menuForUsr .addItem(item4)
menuForUsr .addItem(item5)

menuForUsr.showsStateColumn = true
//menuForUsr.setAccessibilityFrame(NSMakeRect(0, 0, 200, 200))//无效

//不管使用哪个视图(该视图是在Window里面),都会弹出NSMenu菜单
NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.view)//弹出NSMenu菜单

效果:
所有项均不可选,因为未配置各项的actionaction为nil)!
当.onStateImage、.offStateImage尺寸超出后将把字体覆盖掉!而.image尺寸超出后会按照图片大小的尺寸展示出来!

各项均不可选、图片尺寸异常


优化:A.要响应事件必须设置该项的action!B.设置图片时,必须设置图片展示时相应尺寸!(.onStateImage?.size.offStateImage?.size.image?.size)

先添加全局变量:needShowOnline——用来记录是否“展示在线状态”

public var needShowOnline = false //需要“展示在线状态”

优化后,在override func rightMouseDown(with event: NSEvent) {}中的代码:

let menuForUsr = NSMenu(title: "编辑用户")
let item1 = NSMenuItem(title: "设置头像", action: #selector(setUpIcon), keyEquivalent: "")
let item2 = NSMenuItem(title: "基本信息设置", action: #selector(setUpBaseInfo), keyEquivalent: "")
let item3 = NSMenuItem(title: "权限等级申请", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
//设置子菜单及相应选项
let permissionMenu = NSMenu(title: "权限等级申请")
let item3_1 = NSMenuItem(title: "支付权限", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
let item3_2 = NSMenuItem(title: "对话权限", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
permissionMenu .addItem(item3_1)
permissionMenu .addItem(item3_2)
item3.submenu = permissionMenu

let item4 = NSMenuItem(title: "展示在线状态", action: #selector(setUpShowOnline), keyEquivalent: "")
item4.onStateImage = NSImage(named: "usr_item_sel")
item4.offStateImage = NSImage(named: "usr_item_nor")
//设置图片时,必须设置图片展示时相应尺寸!
//当onStateImage、offStateImage尺寸超出后将把字体覆盖掉!而image尺寸超出后会按照图片大小的尺寸展示出来!
let img_W: CGFloat = 15.0
item4.onStateImage?.size = NSMakeSize(img_W, img_W)
item4.offStateImage?.size = NSMakeSize(img_W, img_W)
item4.state = (needShowOnline == true ? NSControl.StateValue.on : NSControl.StateValue.off)
let item5 = NSMenuItem(title: "客户邮件消息", action: #selector(showEmailInfos), keyEquivalent: "")
item5.image = NSImage(named: "usr_item_email")//与标题一起
//当image尺寸超出后将把字体挤开!
item5.image?.size = NSMakeSize(img_W, img_W)
//item5.image?.size = NSMakeSize(img_W+50, img_W+50)

menuForUsr .insertItem(item1, at: 0)    //指定位置插入某一项
menuForUsr .addItem(item2)  //末尾添加某一项
menuForUsr .insertItem(item3, at: (0+2))
menuForUsr .addItem(item4)
menuForUsr .addItem(item5)

menuForUsr.showsStateColumn = true
//menuForUsr.setAccessibilityFrame(NSMakeRect(0, 0, 200, 200))//无效

//不管使用哪个视图(该视图是在Window里面),都会弹出NSMenu菜单
NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.view)//弹出NSMenu菜单

各项NSMenuItem选择的响应事件:

//响应:各项NSMenuItem的事件
@objc func setUpIcon(item: NSMenuItem) {
    print("setUpIcon", "item.title:\(item.title)")
    
}
@objc func setUpPermissionLevelApply(item: NSMenuItem) {
    print("setUpPermissionLevelApply", "item.title:\(item.title)")
    
}
@objc func setUpBaseInfo(item: NSMenuItem) {
    print("setUpBaseInfo", "item.title:\(item.title)")
    
}
@objc func setUpShowOnline(item: NSMenuItem) {
    needShowOnline = !needShowOnline
    print("setUpShowOnline", "item.title:\(item.title)", "needShowOnline:\(needShowOnline)")
    
}
@objc func showEmailInfos(item: NSMenuItem) {
    print("showEmailInfos", "item.title:\(item.title)")
    
}

效果:图片尺寸正常!所有项均可选,响应相应的事件!
选择"展示在线状态"项时,会根据needShowOnline状态来展示是普通状态图片(offStateImage)或选中状态图片(onStateImage)!

Tips:当需要某项 不可以被进行选择

例子:让"权限等级申请"项不可被进行选择!

如果是OC,之前就直接使用如下代码实现了:

item3.enabled = NO; //不可点
 [item3.menu setAutoenablesItems:NO];//不可点

自己使用Swift中各种属性均不能实现:
item3.isEnabled = falseitem3.menu?.autoenablesItems = falseitem3.setAccessibilityEnabled(false)item3.menu?.setAccessibilityEnabled(false)

最后发现了设置某项actionnil时,某项不可以被进行选择
于是进行了如下封装:通过item的title来设置相应的action(也可以使用keyEquivalent)!

func setItemEnable(item: NSMenuItem, enable :Bool) {
   //var actionUse: Selector? = #selector(setUpIcon)
   var actionUse = #selector(setUpIcon)
   switch item.title {//也可以使用item.keyEquivalent
   case "设置头像":
       actionUse = #selector(setUpIcon)
   case "基本信息设置":
       actionUse = #selector(setUpBaseInfo)
   case "权限等级申请":
       actionUse = #selector(setUpPermissionLevelApply)
   case "展示在线状态":
       actionUse = #selector(setUpShowOnline)
   case "客户邮件消息":
       actionUse = #selector(showEmailInfos)
   default:
       break
   }
   if enable {
       item.action = actionUse
   } else {
       item.action = nil
   }
}

调用如下:

self .setItemEnable(item: item3, enable: false)//让"权限等级申请"项不可被选择!

效果:"权限等级申请"项不可被选择!


只响应某个控件的鼠标事件,才会弹出NSMenu菜单

添加全局变量:一张头像的图片

var usrImgV: NSImageView? = nil

放在在视图控制器中:

usrImgV = NSImageView(frame: NSMakeRect(50, 50, 100, 100))
self.view .addSubview(usrImgV!)
usrImgV!.image = NSImage(named: "usr_Set_Icon")
usrImgV!.toolTip = "用户设置"

效果:


使用如下任一代码,都可弹出NSMenu菜单(不管哪个视图(NSView()/self.view/self.usrImgV!)上,都会弹出NSMenu菜单)!

  • 使用NSView()创建一个‘不在Window里面的视图’

    NSMenu .popUpContextMenu(menuForUsr, with: event, for: NSView())//使用‘不在Window里面的视图’,会弹出菜单-但每一项都不可选!
    

效果:鼠标右键点下都会弹出NSMenu菜单,每一项 都不可被选择的!

每一项 都不可被选择的!
  • 使用self.view

    NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.view)//弹出NSMenu菜单
    

    效果:鼠标右键点下都会弹出NSMenu菜单,可选项都是可选择的!

    可选项都是可选择的!
  • 使用self.usrImgV!

    NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.usrImgV!)
    

    效果:鼠标右键点下都会弹出NSMenu菜单,可选项都是可选择的!

    可选项都是可选择的!

结论使用‘在Window里面的视图’,弹出菜单的可选项都是可选的!使用‘不在Window里面的视图’,弹出菜单的每一项都不可选

所以通过.popUpContextMenu方法不可以实现”只响应某个控件的鼠标事件,才会弹出NSMenu菜单“功能!



实现”只响应某个控件的鼠标事件,才会弹出NSMenu菜单“功能:
A.可以通过override func rightMouseDown(with event: NSEvent) { }方法返回的event,得到对应的(NSPoint)!再计算usrImgV相对于Window的尺寸和位置,判断(NSPoint)是否在头像图标(usrImgV)范围x:50到(100+50),y:50(100+50)

思路如此,相关的代码就不书写了~
此思路的方法只适用于视图层次简单的情况!当视图层次繁杂时计算量就吓人了!!😳


B.自定义一个视图类,其内部声明一个用于值传递闭包(该闭包传递该视图NSEvent事件视图控制器)!
原理:在相应的视图里重写并执行override func rightMouseUp(with event: NSEvent) {}”方法,只有在该视图范围内部进行‘鼠标右键按下’操作才会回调该方法返回(NSPoint)信息!
(在各个位置的代码中重写并执行override func rightMouseUp(with event: NSEvent) {}”方法,都只能拿到该点击相对于Window窗口位置信息,所以在哪里获取到(NSPoint)信息一样!)

如下自定义一个图片类UsrImgV

右键选择"New File",创建新文件
选择"Cocoa Class"类型
继承自NSImageView,命名为"UsrImgV"!

在“UsrImgV.swift”文件中的代码:

 import Cocoa

public typealias rightMouseUpClosure = (_ event: NSEvent) -> ()//声明闭包-用于值的传递

class UsrImgV: NSImageView {
    var rightMouseUpBlock : rightMouseUpClosure?//把闭包声明为属性
    
    override func rightMouseUp(with event: NSEvent) {
        let locPt = event .locationInWindow
        print("UsrImgV  point location:",locPt)
        //在各个位置的代码中,都只能拿到相对于Window窗口的位置信息,所以在哪里获取都一样!
        
        rightMouseUpBlock?(event)//调用闭包
        
    }
    
    deinit {
        print("UsrImgV deinit")
        rightMouseUpBlock = nil
        
    }
    

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }
    
}

在ViewController中,将全局变量usrImgV的类型从NSImageView换为UsrImgV!

var usrImgV: NSImageView? = nil

换为

var usrImgV: UsrImgV? = nil

初始化UsrImgV实例及相关配置:

usrImgV = UsrImgV(frame: NSMakeRect(50, 50, 100, 100))
self.view .addSubview(usrImgV!)
usrImgV!.image = NSImage(named: "usr_Set_Icon")
usrImgV!.toolTip = "用户设置"
usrImgV!.rightMouseUpBlock = { [self] (event: NSEvent) -> () in
    let locPt = event .locationInWindow
    print("usrImgV!.rightMouseUpBlock   point location:",locPt)//打印NSPoint信息

    self .createMenuForUser(with: event)
}//闭包回调——对NSEvent事件进行处理

将响应NSEvent事件创建NSMenu菜单的逻辑,封装为如下func createMenuForUser(with event: NSEvent) { }方法:

func createMenuForUser(with event: NSEvent) {
    let menuForUsr = NSMenu(title: "编辑用户")
    let item1 = NSMenuItem(title: "设置头像", action: #selector(setUpIcon), keyEquivalent: "")
    let item2 = NSMenuItem(title: "基本信息设置", action: #selector(setUpBaseInfo), keyEquivalent: "")
    let item3 = NSMenuItem(title: "权限等级申请", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
    //设置子菜单及相应选项
    let permissionMenu = NSMenu(title: "权限等级申请")
    let item3_1 = NSMenuItem(title: "支付权限", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
    let item3_2 = NSMenuItem(title: "对话权限", action: #selector(setUpPermissionLevelApply), keyEquivalent: "")
    permissionMenu .addItem(item3_1)
    permissionMenu .addItem(item3_2)
    item3.submenu = permissionMenu

    let item4 = NSMenuItem(title: "展示在线状态", action: #selector(setUpShowOnline), keyEquivalent: "")
    item4.onStateImage = NSImage(named: "usr_item_sel")
    item4.offStateImage = NSImage(named: "usr_item_nor")
    //设置图片时,必须设置图片展示时相应尺寸!
    //当onStateImage、offStateImage尺寸超出后将把字体覆盖掉!而image尺寸超出后会按照图片大小的尺寸展示出来!
    let img_W: CGFloat = 15.0
    item4.onStateImage?.size = NSMakeSize(img_W, img_W)
    item4.offStateImage?.size = NSMakeSize(img_W, img_W)
    item4.state = (needShowOnline == true ? NSControl.StateValue.on : NSControl.StateValue.off)
    let item5 = NSMenuItem(title: "客户邮件消息", action: #selector(showEmailInfos), keyEquivalent: "")
    item5.image = NSImage(named: "usr_item_email")//与标题一起
    //当image尺寸超出后将把字体挤开!
    item5.image?.size = NSMakeSize(img_W, img_W)
    //item5.image?.size = NSMakeSize(img_W+50, img_W+50)

    menuForUsr .insertItem(item1, at: 0)    //指定位置插入某一项
    menuForUsr .addItem(item2)  //末尾添加某一项
    menuForUsr .insertItem(item3, at: (0+2))
    menuForUsr .addItem(item4)
    menuForUsr .addItem(item5)

    menuForUsr.showsStateColumn = true
    //menuForUsr.setAccessibilityFrame(NSMakeRect(0, 0, 200, 200))//无效

    //不管使用哪个视图(该视图是在Window里面),都会弹出NSMenu菜单
    //NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.view)//弹出NSMenu菜单
    NSMenu .popUpContextMenu(menuForUsr, with: event, for: self.view)//弹出NSMenu菜单
}

效果:

  • usrImgV中鼠标右键按下时,UsrImgV类的func rightMouseUp(with event: NSEvent)方法、ViewContoller类的func rightMouseUp(with event: NSEvent)方法都会回调(都打印了获取(NSPoint)信息——“point location:”和“UsrImgV point location:”)! 菜单的可选项均可选,响应相应的事件!
    此时在ViewContoller中usrImgV!.rightMouseUpBlock闭包部分有回调,用返回的NSEvent事件创建NSMenu菜单
  • self.view中(并且在usrImgV范围外)鼠标右键按下时,仅仅是ViewContoller类的func rightMouseUp(with event: NSEvent)方法会回调(打印了获取(NSPoint)信息——“point location:”)!





之前Mac端的代码是OC写的,现在看感觉好繁杂。。😂

之后尽量用Swift写吧~









goyohol's essay

推荐阅读更多精彩内容