A lightweight Swift package that prompts users to move your macOS app to /Applications/ on first launch.
When macOS apps are launched from Downloads, Desktop, or a mounted DMG, App Translocation runs them from a randomized read-only path. This breaks auto-updates, file associations, and Spotlight indexing. LetsMoveSwiftly fixes this with a single line of code.
Add LetsMoveSwiftly to your Package.swift:
dependencies: [
.package(url: "https://github.com/nicemohawk/lets-move-swiftly.git", from: "1.0.0"),
]Then add it to your target:
.target(name: "MyApp", dependencies: ["LetsMoveSwiftly"]),Or in Xcode: File > Add Package Dependencies... and enter https://github.com/nicemohawk/lets-move-swiftly.git.
import LetsMoveSwiftly
import SwiftUI
@main
struct MyApp: App {
init() {
LetsMoveSwiftly.moveToApplicationsIfNecessary()
}
var body: some Scene {
WindowGroup { ContentView() }
}
}import LetsMoveSwiftly
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillFinishLaunching(_ notification: Notification) {
LetsMoveSwiftly.moveToApplicationsIfNecessary()
}
}That's it. The call:
- Returns immediately if the app is already in
/Applications/, was installed from the Mac App Store, or the user previously chose "Don't Move" - Shows a native dialog with three options: Move to Applications, Not Now, and Don't Move
- Handles the file move (or copy from a read-only DMG), replaces any existing copy, and relaunches from the new location
| Method | Description |
|---|---|
moveToApplicationsIfNecessary() |
Main entry point. Call once at launch. |
shouldOfferToMove(...) |
Pure decision logic. All parameters are injectable for testing. |
isInApplicationsFolder(_:) |
Checks if a path is inside /Applications/ or ~/Applications/. |
relocateBundle(from:to:...) |
Moves or copies an app bundle to a destination directory. |
dontAskAgainKey |
The UserDefaults key for the "Don't Move" preference. Reset it to prompt again. |
- App Store detection: Checks if a valid App Store receipt file exists on disk. If so, the prompt is skipped entirely (the App Store handles placement).
- Move vs. copy: If the source directory is writable (ZIP extraction), the app is moved. If read-only (mounted DMG), it's copied.
- Existing app replacement: If a copy already exists in
/Applications/, the user is asked to confirm before replacing. - Relaunch: After moving, the app launches from its new location and the old process terminates.
- macOS 13.0+ (Ventura)
- Swift 5.9+
- Xcode 15+
Inspired by LetsMove (public domain, Objective-C) and AppMover (Swift). Both libraries implemented this pattern but lack Swift Package Manager support. LetsMoveSwiftly is a simple, modern reimplementation built for SPM from the start.
Contributions are welcome! See CONTRIBUTING.md for guidelines. Moar PRs plz.
MIT License. See LICENSE for details.