Reputation: 137
I am building an macOS app for the menu bar and it should automatically start with system start. I started with implementing the autostart functionality for a standard window based macOS app following this tutorial this tutorial. I have
skip install
to yes
for the helper appServiceManagement.framework
That worked fine, the app launched automatically :) So I started changing the project, that the main application becomes a menu bar app. However than, the app wouldn't auto launch anymore :/ Does someone have a solution for that?
Heres the code of the app delegate of the main 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 { $0.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")
}
And this is the app delegate of the helper app:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.####.####"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.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")
}
Thank you very much for your help :)
Upvotes: 2
Views: 1072
Reputation: 620
My code looks pretty much the same, except how I compose the path in the helper app:
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)
Also, if I remember correctly, I had to make sure the Main.storyboard file still had the "Application Scene" with an Application object and an empty main menu.
Upvotes: 5