Join our newsletter! Get Swift & SwiftUI tips, project updates, and discounts on our books...JOIN OUR NEWSLETTER!Monthly Swift insights, updates, and deals...

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.

Screenshot of the Settings panel of EncodeDecode

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.

Screenshot of the Login Item Added notification

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.

Screenshot of the Login Items and Extensions settings


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.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35