MacOS开发系列6-自定义菜单栏NSMenuItem

2,666 阅读3分钟

继上一篇已经介绍了如何创建一个无Window、不在Dock上显示、并且只在status bar显示的APP后,现在就开始介绍一下如自定义菜单栏,让菜单栏可以更加实用和更加个性化。

PS:因为是续上一篇文章的,如果需要回顾或者有什么不明白的,可以看看我上一篇文章

文章跳转 ->【MacOS开发系列5-创建StatusBarApp(无窗体、不在Dock显示,只在状态显示)

自定义菜单视图

demo代码已经上传到GitHub传送门

为了方便阅读,我把菜单栏简化了,只剩下AboutQuit.同时又添加了一个新的MenuItem

同样的,我们在为这个新的MenuItem添加IBOutlet到AppDelegate中。

@IBOutlet weak var firstMenuItem: NSMenuItem!

竟然是自定义的菜单视图,所以我们要创建一个NSView。里面的内容你们喜欢。我这里就随便搞搞了。

以下就是我的自定义的View的代码,就很简单就是一个居中显示的label,内容为当前时间:

然后就可以把自定义view的设置到需要自定义菜单栏里面。

回到AppDelegate中的awakeFromNib这个函数。

override func awakeFromNib() {
        
       super.awakeFromNib()
       print("awakeFromNib")
       statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
       statusItem?.button?.title = "StatusBarAppExample"
            
       let itemImage = NSImage(named: "SplitScreen")
       itemImage?.isTemplate = true
       statusItem?.button?.image = itemImage
       if let menu = menu {
          statusItem?.menu = menu
       }
        
        if let item = firstMenuItem {
            let cView = CustomItemView.init(frame: NSRect.init(x: 0, y: 0, width:  menu.size.width, height: 200))
            item.view = cView
        }
    }

完整AppDelegate如下:

这样就完成了自定义菜单栏了,效果如下:

但是如果想弄成和某宝旗下的旺旺一样的效果呢?

某宝的旺旺App的界面:

那就要使用NSPopover了。那现在就开始介绍如何使用NSPopover进行自定义菜单栏NSMenuItem

NSPopover自定义菜单视图

demo代码已经上传到GitHub传送门

因为现在只需要点击一下StatusBar上的icon就出现我们NSPopover,所以菜单栏可以全部删除。效果如下:

然后在Storyboard上新建一个ViewController,用于在NSPopover上显示的。如图:

然后新建一个PopoverViewController.swift文件,对应Storyboard上新建的ViewController。如图:

因为在显示StoryboardViewController的时候,需要一个ViewControllerID。所以需要在Storyboard上的ViewController上对应起来。如图:

然后就回到AppDelegate.swift上写代码了.

  1. AppDelegate声明NSStatusItemNSPopoverPopoverViewController对象:如下:
var statusItem: NSStatusItem?
     
var popover: NSPopover?
     
var popoverVC: PopoverViewController?
  1. AppDelegate中添加showPopoverVC这个函数,用于在NSPopover显示PopoverViewController
@objc func showPopoverVC(){
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        guard let vc = storyboard.instantiateController(withIdentifier: .init(stringLiteral: "PopoverViewControllerID")) as? PopoverViewController
        else { return }
        
        popoverVC = vc
        
        if let pop = popover, let button = statusItem?.button{
            pop.contentViewController = popoverVC
            popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
        }
        
    }
  1. 重写AppDelegate中的awakeFromNib这个函数。
override func awakeFromNib() {
  super.awakeFromNib()

  statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
  statusItem?.button?.title = "StatusBarAppWithPopoverExample"
             
  let itemImage = NSImage(named: "SplitScreen")
  itemImage?.isTemplate = true
  statusItem?.button?.image = itemImage
  statusItem?.button?.target = self
  statusItem?.button?.action = #selector(showPopoverVC)
  
  popover = NSPopover()
  popover?.behavior = .transient
}

完整AppDelegate如下:

这样就完成了使用NSPopover进行自定义菜单栏了。效果如图:

最后,在PopoverViewController.swift里面为打开主窗口的按钮设置IBAction,然后写上如下代码:

@IBAction func showMainVc(_ sender: NSButton) {
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        guard let vc = storyboard.instantiateController(withIdentifier: .init(stringLiteral: "MainViewControllerID")) as? MainViewController
        else { return }
        
        guard let appDelegate = NSApplication.shared.delegate as? AppDelegate, let pop = appDelegate.popover else { return }
        
        
        let window = NSWindow(contentViewController: vc)
        window.makeKeyAndOrderFront(sender)
        window.level = .floating
        window.title = "Main View Controller"
        var centerX : CGFloat = 0.0
        var centerY : CGFloat = 0.0
        let width : CGFloat = 534.0
        let height : CGFloat = 319.0
            
        if let screen = window.screen {
            centerX = screen.frame.midX - width / 2.0
            centerY = screen.frame.midY - height / 2.0
        }
        window.setFrame(CGRect.init(x: centerX, y: centerY, width: width, height: height), display:true)
        pop.close()
    }

其中MainViewController的创建和PopoverViewController一致。具体参考上面的即可.

对于在NSPopover显示的ViewController的点击事件的时候,如果需要操作后,关闭NSPopover,一定要调用close()方法。不然会一直显示的:

pop.close()