macOS的menuBar功能非常強大,我們在開發macOS應用的時候,經常需要利用menuBar實現功能。然而網上關於menuBar開發的文檔卻甚少,更別提用上SwiftUI開發的。這篇文章從以下幾部分講解用SwiftUI開發menuBar/statusBar的方法,希望對大家有幫助:
一、在statusBarItem彈出Popover
二、更改Popover背景色
三、在statusBar上控制主窗口的開關
四、在statusBar上控制statusBar的顯隱/開關
一、在statusBarItem彈出Popover
statusBar彈出menu是比較簡單的,但是如果我們要做一些定製,就需要用到Popover了
創建Popover
在AppDelegate文件中,聲明一個NSPopover變量(下面的statusBarItem之後會用到)
var popover: NSPopover! var statusBarItem: NSStatusItem!
隨後,在applicationDidFinishLaunching中, ContentView 初始化之後,加上
// Create the popover
let popover = NSPopover()
popover.contentSize = NSSize(width: 400, height: 400)
popover.behavior = .transient //代表用戶點擊其他區域時popover自動消失 popover.contentViewController = NSHostingController(rootView: contentView) self.popover = popover
此時我們的AppDelegate文件應該長這樣:
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the popover
let popover = NSPopover()
popover.contentSize = NSSize(width: 400, height: 500)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
}
}
這樣我們即創建了一個Popover視圖以及它的控制器,這個Popover的視圖內容是我們的ContentView.swift裏的內容。這樣我們就可以在ContentView裏用SwiftUI自由地定製我們的Popover。接下來我們要做的是,創建一個menuBarItem,當用戶點擊menuBarItem的按鈕時,彈出我們已經寫好的Popover。
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
button.image = NSImage(named: "Icon")
button.action = #selector(togglePopover(_:))
}
還記得我們一開始聲明瞭
var statusBarItem: NSStatusItem!
現在我們需要創建它,同時給它在menuBar設置個圖標,綁定點擊事件爲togglePopover
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
statusBarItem.button?.title = "⏳"
button.action = #selector(togglePopover(_:))
}
綁定togglePopover是爲了在用戶點擊時彈出Popover
// Create the status item
@objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
最終,我們的AppDelegate文件長這樣
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the popover
let popover = NSPopover()
popover.contentSize = NSSize(width: 400, height: 500)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
// Create the status item
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
statusBarItem.button?.title = "⏳"
button.action = #selector(togglePopover(_:))
}
}
@objc func togglePopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button {
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
二、更改Popover背景色
我們創建出來的Popover默認的背景色是透明的,很容易與我們的UI不搭配。如果需要定製背景顏色,目前我沒有發現直接用SwiftUI實現的方法。我們需要去拓展NSPopover類,在新建實例的時候指定背景色。
首先,拓展NSPopover類:
import Cocoa
import SwiftUI
extension NSPopover {
private struct Keys {
static var backgroundViewKey = "backgroundKey"
}
private var backgroundView: NSView {
let bgView = objc_getAssociatedObject(self, &Keys.backgroundViewKey) as? NSView
if let view = bgView {
return view
}
let view = NSView()
objc_setAssociatedObject(self, &Keys.backgroundViewKey, view, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
NotificationCenter.default.addObserver(self, selector: #selector(popoverWillOpen(_:)), name: NSPopover.willShowNotification, object: nil)
return view
}
@objc private func popoverWillOpen(_ notification: Notification) {
if backgroundView.superview == nil {
if let contentView = contentViewController?.view, let frameView = contentView.superview {
frameView.wantsLayer = true
backgroundView.frame = NSInsetRect(frameView.frame, 1, 1)
backgroundView.autoresizingMask = [.width, .height]
frameView.addSubview(backgroundView, positioned: .below, relativeTo: contentView)
}
}
}
var backgroundColor: NSColor? {
get {
if let bgColor = backgroundView.layer?.backgroundColor {
return NSColor(cgColor: bgColor)
}
return nil
}
set {
backgroundView.wantsLayer = true
backgroundView.layer?.backgroundColor = newValue?.cgColor
}
}
}
在AppDelegate初始化時,增加background參數
let popover = NSPopover()
popover.backgroundColor = NSColor.white //設置popover顏色,對應extension裏對popover的改寫
popover.contentSize = NSSize(width: 350, height: 400)
搞定!
三、在statusBar增加控制主窗口開關和插件開關
現在statusBarItem的視圖文件中加入按鈕
HStack{
Text("Time Capsule").font(Font.system(.headline)).bold()
Spacer()
MenuButton(label: Image("moreBlack").resizable().frame(width:20,height:20)) {
Button(action: {
showWindow()
}) {
Text("打開主窗口")
}
Button(action: {
hideStatusBar()
}) {
Text("退出")
}
}.menuButtonStyle(BorderlessButtonMenuButtonStyle())
}
.padding([.top, .trailing], 18).padding(.leading,30)
接下來我們要補充兩個按鈕點擊事件showWindow()和hideStatusBar()的方法
func showWindow() {
NSApp.unhide(nil)
func hideStatusBar() {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.statusBarItem.isVisible = false
}
NSApp.unhide方法可以直接顯示我們的主程序,hide則可以隱藏主程序
statusBarItem.isVisible設爲false則可以直接讓我們的插件消失