如何为 macOS 菜单栏应用程序启用自动启动?
How enable autostart for a macOS menu bar app?
我正在为菜单栏构建一个 macOS 应用程序,它应该会随系统启动自动启动。
我开始按照本教程 this tutorial 为基于标准 window 的 macOS 应用程序实现自动启动功能。我有
- 在主项目(助手应用程序)中添加了一个新目标
- 将助手应用程序的
skip install
更改为 yes
- 将助手应用设置为仅后台应用
- 向主应用程序添加了一个新的复制文件构建阶段,以将辅助应用程序复制到包中
- link编辑了
ServiceManagement.framework
- 在应用程序委托中实现了帮助应用程序随系统启动一起启动的功能。启动后,它会启动主应用程序(请参阅教程 link 了解更多信息或下面的源代码)
效果很好,应用程序自动启动 :) 所以我开始更改项目,将主应用程序变成菜单栏应用程序。但是,该应用程序将不再自动启动:/有人对此有解决方案吗?
这是主应用程序的 app 委托的代码:
import Cocoa
import ServiceManagement
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.button?.title = "Test"
statusItem.button?.target = self
statusItem.button?.action = #selector(showWindow)
// auto start
let launcherAppId = "com.####.####Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { [=10=].bundleIdentifier == launcherAppId }.isEmpty
SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
@objc func showWindow() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unable to find main view controller")
}
guard let button = statusItem.button else {
fatalError("Unable to find status item button")
}
let popover = NSPopover()
popover.contentViewController = vc
popover.behavior = .transient
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
这是助手应用程序的 app 委托:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.####.####"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { [=11=].bundleIdentifier == mainAppIdentifier }.isEmpty
if !isRunning {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast()
components.removeLast()
components.removeLast()
components.append("MacOS")
components.append("####") //main app name
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
@objc func terminate() {
NSApp.terminate(nil)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
非常感谢您的帮助:)
除了我在助手应用程序中编写路径的方式外,我的代码看起来几乎相同:
var pathComponents = (Bundle.main.bundlePath as NSString).pathComponents
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
let newPath = NSString.path(withComponents: pathComponents)
NSWorkspace.shared.launchApplication(newPath)
此外,如果我没记错的话,我必须确保 Main.storyboard 文件仍然有 "Application Scene" 以及一个应用程序对象和一个空的主菜单。
我正在为菜单栏构建一个 macOS 应用程序,它应该会随系统启动自动启动。 我开始按照本教程 this tutorial 为基于标准 window 的 macOS 应用程序实现自动启动功能。我有
- 在主项目(助手应用程序)中添加了一个新目标
- 将助手应用程序的
skip install
更改为yes
- 将助手应用设置为仅后台应用
- 向主应用程序添加了一个新的复制文件构建阶段,以将辅助应用程序复制到包中
- link编辑了
ServiceManagement.framework
- 在应用程序委托中实现了帮助应用程序随系统启动一起启动的功能。启动后,它会启动主应用程序(请参阅教程 link 了解更多信息或下面的源代码)
效果很好,应用程序自动启动 :) 所以我开始更改项目,将主应用程序变成菜单栏应用程序。但是,该应用程序将不再自动启动:/有人对此有解决方案吗?
这是主应用程序的 app 委托的代码:
import Cocoa
import ServiceManagement
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.button?.title = "Test"
statusItem.button?.target = self
statusItem.button?.action = #selector(showWindow)
// auto start
let launcherAppId = "com.####.####Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { [=10=].bundleIdentifier == launcherAppId }.isEmpty
SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
@objc func showWindow() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unable to find main view controller")
}
guard let button = statusItem.button else {
fatalError("Unable to find status item button")
}
let popover = NSPopover()
popover.contentViewController = vc
popover.behavior = .transient
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
这是助手应用程序的 app 委托:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.####.####"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { [=11=].bundleIdentifier == mainAppIdentifier }.isEmpty
if !isRunning {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast()
components.removeLast()
components.removeLast()
components.append("MacOS")
components.append("####") //main app name
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
@objc func terminate() {
NSApp.terminate(nil)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
非常感谢您的帮助:)
除了我在助手应用程序中编写路径的方式外,我的代码看起来几乎相同:
var pathComponents = (Bundle.main.bundlePath as NSString).pathComponents
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
let newPath = NSString.path(withComponents: pathComponents)
NSWorkspace.shared.launchApplication(newPath)
此外,如果我没记错的话,我必须确保 Main.storyboard 文件仍然有 "Application Scene" 以及一个应用程序对象和一个空的主菜单。