On this tutorial I am going to present you tips on how to launch a very sandboxed macOS utility on system startup written in Swift.
WARN: Replace: you need to merely add the LaunchAtLogin library to your undertaking. It’ll handle all the things and it has another cool utility options.
Venture setup
Let’s begin this tutorial by creating a brand new Xcode undertaking with a macOS app template. Identify it for instance MainApplication, use storyboards and naturally choose Swift because the default language, we don’t want checks for this undertaking in any respect.
Now that we have now the primary utility goal, there may be this good little perform obtainable referred to as SMLoginItemSetEnabled
. With that perform you’ll be able to register an utility bundle identifier to auto begin when the consumer logs in, however you cannot register your individual app identifier. Sounds loopy, huh? 😜
You’ll be able to register a bundle identifier embedded into your principal utility to get auto-launched by the system. To do that you’ll have to create a brand new launcher utility which will likely be launched later by your principal utility.
You additionally need to code signal your utility together with your Developer ID, in any other case it gained’t begin after you log in to macOS. Sandboxing is a vital a part of the method, so just be sure you observe each instruction fastidiously.
Targets & configurations
Create a brand new goal inside your present undertaking. Identify this new goal for instance LauncherApplication. Allow sandbox and code signing for each targets (principal and launcher apps) below the Signing & Capabilities tab. For the LauncherApplication goal within the construct settings set skip set up to sure.
For the launcher app add a brand new entry to the Data.plist file: Utility is background solely with the worth: sure. It will set your utility as a background app, we don’t really want consumer interface for a launcher device, proper?
Add a brand new copy file construct part to your principal utility goal to repeat your launcher utility into the bundle. The vacation spot needs to be wrapper and the subpath needs to be Contents/Library/LoginItems
.
Hyperlink the ServiceManagement.framework
to your principal utility and double verify that the launcher app is embedded into your principal utility.
From the LauncherApplication
‘s storyboard file delete your window and your view controller, additionally you’ll be able to take away the ViewController.swift
file from this goal. It is a background app in spite of everything, so we don’t want these silly issues to put round.
Creating the launcher programmatically
Someplace in your principal utility you must register your launcher utility’s identifier. When your principal utility begins you must kill the launcher utility if it’s nonetheless operating. You are able to do this by sending a notification to that particular app with the NSDistributedNotificationCenter
class.
import Cocoa
import ServiceManagement
extension Notification.Identify {
static let killLauncher = Notification.Identify("killLauncher")
}
@NSApplicationMain
class AppDelegate: NSObject {}
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let launcherAppId = "com.tiborbodecs.LauncherApplication"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter {
$0.bundleIdentifier == launcherAppId
}.isEmpty
SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().submit(
title: .killLauncher,
object: Bundle.principal.bundleIdentifier!
)
}
}
}
Within the launcher utility you must begin your principal utility if it’s not operating already. That’s it. You must also subscribe for the notifications from the primary app to terminate if the launcher shouldn’t be wanted anymore.
import Cocoa
extension Notification.Identify {
static let killLauncher = Notification.Identify("killLauncher")
}
@NSApplicationMain
class AppDelegate: NSObject {
@objc func terminate() {
NSApp.terminate(nil)
}
}
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.tiborbodecs.MainApplication"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter {
$0.bundleIdentifier == mainAppIdentifier
}.isEmpty
if !isRunning {
DistributedNotificationCenter.default().addObserver(
self,
selector: #selector(self.terminate),
title: .killLauncher,
object: mainAppIdentifier
)
let path = Bundle.principal.bundlePath as NSString
var elements = path.pathComponents
elements.removeLast()
elements.removeLast()
elements.removeLast()
elements.append("MacOS")
elements.append("MainApplication") //principal app title
let newPath = NSString.path(withComponents: elements)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
}
That’s it, we’re able to launch. Export your principal utility and right here is an important factor: code signal it together with your Developer ID. Begin it, shut it, sign off and again into the system. Hopefully your principal utility will likely be operating once more.