Add launch at login setting to a macOS app
I recently built a macOS menu bar utility for URL encoding and decoding strings called EncodeDecode, and I thought it would be nice to include a setting to launch the app at login for users who might need frequent access to it. In this post, I'll show how this can be easily implemented using SMAppService.
It's important to note that it's best to have an explicit setting for this functionality in the app, set to false
by default, to avoid any issues with App Store review. According to Apple's App Review Guidelines, Mac apps may not auto-launch or execute code at startup or login without user consent.
I included a toggle to control this setting within the Settings panel of my app.

Here's the UI code for that section:
struct LaunchAtLoginSettingsSection: View {
@Environment(AppState.self) var appState
var body: some View {
@Bindable var appState = appState
Section {
VStack(alignment: .leading, spacing: 8) {
Toggle("Launch at login", isOn: $appState.launchAtLogin)
Text("""
Add EncodeDecode app to the menu bar automatically \
when you log in on your Mac.
""")
.font(.callout)
.foregroundStyle(.secondary)
}
}
}
}
@Observable
class AppState {
var launchAtLogin = false
}
To use the SMAppService
APIs, we need to import the ServiceManagement
framework. Once imported, we can register the app service object corresponding to the main application as a login item when the user enables the setting, and unregister it when the setting is disabled.
import ServiceManagement
struct LaunchAtLoginSettingsSection: View {
@Environment(AppState.self) var appState
var body: some View {
...
Section {
...
}
.onChange(of: appState.launchAtLogin) { _, newValue in
if newValue == true {
try? SMAppService.mainApp.register()
} else {
try? SMAppService.mainApp.unregister()
}
}
}
}
When the user enables the setting and we register the app with SMAppService
, they will see a notification informing them that a login item was added.

Users can view and manage their login items in the System Settings. They can also remove our app from the login items list if they wish.

The SMAppService
API allows us to check the current status of our app, so we can use it to ensure the UI is up to date.
import ServiceManagement
struct LaunchAtLoginSettingsSection: View {
@Environment(AppState.self) var appState
var body: some View {
...
Section {
...
}
.onAppear {
if SMAppService.mainApp.status == .enabled {
appState.launchAtLogin = true
} else {
appState.launchAtLogin = false
}
}
}
}
In most cases, checking the status in onAppear()
should be sufficient, as it’s unlikely that the user would modify this setting in the System Settings while the app's settings window is open. However, if that does happen, we can update the UI state when the settings window regains focus by using the appearsActive
environment value. This ensures that when the user refocuses on the settings window, the correct system settings status is reflected.
import ServiceManagement
struct LaunchAtLoginSettingsSection: View {
@Environment(AppState.self) var appState
@Environment(\.appearsActive) var appearsActive
var body: some View {
...
Section {
...
}
.onChange(of: appearsActive) { _, newValue in
guard newValue else { return }
if SMAppService.mainApp.status == .enabled {
appState.launchAtLogin = true
} else {
appState.launchAtLogin = false
}
}
}
}
Since users can remove our app from the login items at any time, it’s important to read the status from SMAppService
rather than storing this setting locally in the app, to ensure that our UI reflects the most recent state.
If you're an experienced Swift developer looking to learn advanced techniques, check out my latest book Swift Gems. It’s packed with tips and tricks focused solely on the Swift language and Swift Standard Library. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice that will elevate your Swift development skills to the next level. Grab your copy and let's explore these advanced techniques together.