On this tutorial I am going to present you how you can launch a totally sandboxed macOS utility on system startup written in Swift 5.
Swift
Undertaking setup
Let’s begin this tutorial by creating a brand new Xcode venture with a macOS app template. Identify it for instance MainApplication, use storyboards and naturally choose Swift because the default language, we do not want assessments for this venture in any respect.
Now that we’ve got the principle utility goal, there may be this good little perform obtainable known 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 personal app identifier. Sounds loopy, huh? 😜
You may register a bundle identifier embedded into your predominant utility to get auto-launched by the system. To do that you’ll have to create a brand new launcher utility which will probably be launched later by your predominant utility.
You additionally need to code signal your utility along with your Developer ID, in any other case it will not begin after you log in to macOS. Sandboxing is a vital a part of the method, so just remember to comply with each instruction rigorously.
Targets & configurations
Create a brand new goal inside your present venture. Identify this new goal for instance LauncherApplication. Allow sandbox and code signing for each targets (predominant and launcher apps) underneath 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. This can set your utility as a background app, we do not really want consumer interface for a launcher software, proper?
Add a brand new copy file construct section to your predominant utility goal to repeat your launcher utility into the bundle. The vacation spot ought to be wrapper and the subpath ought to be Contents/Library/LoginItems
.
Hyperlink the ServiceManagement.framework
to your predominant utility and double verify that the launcher app is embedded into your predominant 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 do not want these silly issues to put round.
Creating the launcher programmatically
Someplace in your predominant utility you must register your launcher utility’s identifier. When your predominant utility begins you must kill the launcher utility if it is nonetheless working. 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().put up(title: .killLauncher, object: Bundle.predominant.bundleIdentifier!)
}
}
}
Within the launcher utility you must begin your predominant utility if it is not working already. That is it. You also needs to subscribe for the notifications from the principle app to terminate if the launcher will not 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.predominant.bundlePath as NSString
var parts = path.pathComponents
parts.removeLast()
parts.removeLast()
parts.removeLast()
parts.append("MacOS")
parts.append("MainApplication")
let newPath = NSString.path(withComponents: parts)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
}
That is it, we’re able to launch. Export your predominant utility and right here is crucial factor: code signal it along with your Developer ID. Begin it, shut it, log off and again into the system. Hopefully your predominant utility will probably be working once more.