• macOS Swift 原生项目集成 Python3 运转环境

最近想开发一个根据 Menu Bar 的项目,然后把 NSWindowController 作为内容展示和交互。看到很多的教程都是在 Menu Bar 上添加 NSPopover,然后合作 NSViewController 作业,这个计划会在 Menu Bar 下显现 NSPopover 的箭头,不符合我的需求,我想要的效果是 macOS 系统调理音量、挑选输入法这种相似的窗口。

macOS Menu Bar + NSWindowController

一、新建一个 Menu Bar 的 macOS 项目

打开 Main.storyboard,删去 WindowController 和 ViewController

macOS Menu Bar + NSWindowController

在 info 中添加一个配置 Application is agent (UIElement),设置为 YES

macOS Menu Bar + NSWindowController

二、创立 NSStatusItem

拖入 Menu Bar 的 icon,新建 StatusBarMenu 文件,代码如下

import AppKit
class StatusBarMenu: NSObject {
    var statusBarItem: NSStatusItem!
    override init() {
        super.init()
        initStatusBarItem()
    }
    // 初始化状态栏Button
    func initStatusBarItem() {
        self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.squareLength))
        if let button = self.statusBarItem.button {
            button.image = NSImage(named: "menubar")
            button.image?.size = NSSize(width: 18.0, height: 18.0)
            button.image?.isTemplate = true
            button.action = #selector(AppDelegate.instance.togglePop)
            button.sendAction(on: [.leftMouseUp, .rightMouseUp])
        }
    }
}

三、AppDelegate 中去运用 NSStatusItem

// 声明特点
static private(set) var instance: AppDelegate! = nil
var statusMenu: StatusBarMenu?
var showPopWindow = false // 记录是否显现主窗口
func applicationDidFinishLaunching(_ aNotification: Notification) {
    AppDelegate.instance = self
    self.statusMenu = StatusBarMenu()
}
// MARK: 显现和躲藏窗口
@objc func togglePop() {
}

现在运转项目,能够在顶部菜单栏看到 Menu Bar 的 icon 了。

四、创立主窗口 PopWindowController

在 PopWindowController.xib 中拖入 Visual Effect View

macOS Menu Bar + NSWindowController

macOS Menu Bar + NSWindowController

Window 设置

macOS Menu Bar + NSWindowController

import Cocoa
class PopWindowController: NSWindowController {
    override var windowNibName: NSNib.Name {
        return NSNib.Name("PopWindowController")
    }
    override func windowDidLoad() {
        super.windowDidLoad()
    }
}

回到 AppDelegate 中运用 PopWindowController

// 声明特点
lazy var popWindowController = PopWindowController()
@objc func togglePop() {
    if !showPopWindow {
        // 计算窗口的方位
        if let event = NSApp.currentEvent, let window = event.window, let popWindow = popWindowController.window  {
            let eventFrame = window.frame
            let eventOrigin = eventFrame.origin
            let popWindowTopLeftPosition = CGPoint(x: eventOrigin.x, y: eventOrigin.y)
            popWindow.setFrameTopLeftPoint(popWindowTopLeftPosition)
            popWindow.makeKeyAndOrderFront(self)
            NSApp.activate(ignoringOtherApps: true)
            showPopWindow = true
        }
    } else {
        if let popWindow = popWindowController.window {
            popWindow.orderOut(nil)
            showPopWindow = false
        }
    }
}

这个时候运转,能够看到窗口已经出现,但是窗口是可拖动的,这显着不合适的。 处理办法是在 PopWindowController 中设置窗口不可移动。

import Cocoa
class PopWindowController: NSWindowController {
    override var windowNibName: NSNib.Name {
        return NSNib.Name("PopWindowController")
    }
    override func windowDidLoad() {
        super.windowDidLoad()
        // 窗口不可移动
        window?.isMovable = false
    }
}

macOS Menu Bar + NSWindowController

五、监控鼠标点击其他 Menu Bar

到目前为止,全体效果已经实现。点击菜单栏其他的图标时,我们的窗口没有消失,这个问题需要处理。

新增两个通知,当点击其他菜单时,调用窗口的躲藏办法即可。

func applicationDidFinishLaunching(_ aNotification: Notification) {
        AppDelegate.instance = self
        self.statusMenu = StatusBarMenu()
        NotificationCenter.default.addObserver(forName: NSWindow.didResignKeyNotification, object: nil, queue: OperationQueue.main) { _ in
            self.togglePop()
        }
        NotificationCenter.default.addObserver(forName: NSWindow.didResignMainNotification, object: nil, queue: OperationQueue.main) { _ in
            self.togglePop()
        }
}

完好 demo 下载