Skip to content

Add “Launch at Login” functionality to your macOS app in seconds

License

Notifications You must be signed in to change notification settings

sindresorhus/LaunchAtLogin-Legacy

Repository files navigation

LaunchAtLogin

Add “Launch at Login” functionality to your macOS app in seconds

If your app targets macOS 13 or later, check out this modern version instead.

It's usually quite a convoluted and error-prone process to add this (on macOS 12 and older). No more!

This package works with both sandboxed and non-sandboxed apps and it's App Store compatible and used in apps like Plash, Dato, Lungo, and Battery Indicator.

This package uses the new SMAppService on macOS 13+ and SMLoginItemSetEnabled on older macOS versions.

Why should I use this package now that SMAppService exists?

  • Backwards compatibility with older macOS versions
  • Nicer API
  • Included SwiftUI component

Requirements

macOS 10.13+

Install

Add https://github.com/sindresorhus/LaunchAtLogin-Legacy in the “Swift Package Manager” tab in Xcode.

Usage

Skip this step if your app targets macOS 13 or later.

Add a new “Run Script Phase” below (not into) “Copy Bundle Resources” in “Build Phases” with the following:

"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh"

And uncheck “Based on dependency analysis”.

The build phase cannot run with "User Script Sandboxing" enabled. With Xcode 15 or newer where it is enabled by default, disable "User Script Sandboxing" in build settings.

(It needs some extra works to have our script to comply with the build phase sandbox.) (I would name the run script Copy “Launch at Login Helper”)

Use it in your app

No need to store any state to UserDefaults.

Note that the Mac App Store guidelines requires “launch at login” functionality to be enabled in response to a user action. This is usually solved by making it a preference that is disabled by default. Many apps also let the user activate it in a welcome screen.

As static property

import LaunchAtLogin

print(LaunchAtLogin.isEnabled)
//=> false

LaunchAtLogin.isEnabled = true

print(LaunchAtLogin.isEnabled)
//=> true

SwiftUI

This package comes with a LaunchAtLogin.Toggle view which is like the built-in Toggle but with a predefined binding and label. Clicking the view toggles “launch at login” for your app.

struct ContentView: View {
	var body: some View {
		LaunchAtLogin.Toggle()
	}
}

The default label is "Launch at login", but it can be overridden for localization and other needs:

struct ContentView: View {
	var body: some View {
		LaunchAtLogin.Toggle {
			Text("Launch at login")
		}
	}
}

Alternatively, you can use LaunchAtLogin.observable as a binding with @ObservedObject:

import SwiftUI
import LaunchAtLogin

struct ContentView: View {
	@ObservedObject private var launchAtLogin = LaunchAtLogin.observable

	var body: some View {
		Toggle("Launch at login", isOn: $launchAtLogin.isEnabled)
	}
}

Combine

Just subscribe to LaunchAtLogin.publisher:

import Combine
import LaunchAtLogin

final class ViewModel {
	private var isLaunchAtLoginEnabled = LaunchAtLogin.isEnabled
	private var cancellables = Set<AnyCancellable>()

	func bind() {
		LaunchAtLogin
			.publisher
			.assign(to: \.isLaunchAtLoginEnabled, on: self)
			.store(in: &cancellables)
	}
}

Swift Concurrency

Use LaunchAtLogin.publisher.values.

Storyboards

Bind the control to the LaunchAtLogin.kvo exposed property:

import Cocoa
import LaunchAtLogin

final class ViewController: NSViewController {
	@objc dynamic var launchAtLogin = LaunchAtLogin.kvo
}

How does it work?

On macOS 12 and earlier, the package bundles the helper app needed to launch your app and copies it into your app at build time. On macOS 13 and later, it calls the built-in API.

FAQ

I'm getting a “No such file or directory” error when archiving my app

Please ensure that the LaunchAtLogin run script phase is still below the “Embed Frameworks” phase. The order could have been accidentally changed.

The build error usually presents itself as:

cp: […]/Resources/LaunchAtLoginHelper.app: No such file or directory
rm: […]/Resources/copy-helper.sh: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code

My app doesn't show up in “System Preferences › Users & Groups › Login Items”

This is the expected behavior, unfortunately.

However, it will show there on macOS 13 and later.

My app doesn't launch at login when testing

This is usually caused by having one or more older builds of your app laying around somewhere on the system, and macOS picking one of those instead, which doesn't have the launch helper, and thus fails to start.

Some things you can try:

  • Bump the version & build of your app so macOS is more likely to pick it.
  • Delete the DerivedData directory.
  • Ensure you don't have any other builds laying around somewhere.

Some helpful Stack Overflow answers:

I can't see the LaunchAtLogin.bundle in my debug build or I get a notarization error for developer ID distribution

As discussed here, this package tries to determine if you're making a release or debug build and clean up its install accordingly. If your debug build is missing the bundle or, conversely, your release build has the bundle and it causes a code signing error, that means this has failed.

The script's determination is based on the “Build Active Architecture Only” flag in build settings. If this is set to YES, then the script will package LaunchAtLogin for a debug build. You must set this flag to NO if you plan on distributing the build with codesigning.

Related