Integrating Sparkle Updater in SwiftUI for macOS

Alessandro Bortoluzzi
4 min readAug 30, 2023

--

Delivering apps outside the App Store, and their updates, can seem tricky. But with Sparkle, an easy-to-use open-source tool, it becomes simple. In this guide, I’ll share the steps, focusing on clear explanations. If I had trouble with it before, I’m here to make sure you won’t!

Preparing for Hosting

GitHub, well known for its code repositories, can also act as an excellent hosting platform. You can use services like DigitalOcean Spaces, Amazon S3, or Google Cloud Storage. But GitHub is easy and free.

Begin by setting up a new public GitHub repository. Within the settings of your repository, activate GitHub Pages.

Onboarding Sparkle

Add Sparkle Package

Add Sparkle to your project using its repository URL from Xcode > File > Add Package Dependencies: https://github.com/sparkle-project/Sparkle

Generating EdDSA Signatures

For security purposes, we need to sign every update. In Xcode, find the Sparkle package, right-click it, and pick “Show in Finder”. Once there, go back one folder to “artifacts”. Then, go to `sparkle/Sparkle/bin/generate_keys` to use the tool. This will generate a private key, which will be safely stored in your Mac’s Keychain, and a public key. It’s essential to remember that these keys should only be generated once. The public key will be used in the next steps, so keep it within reach.

Sparkle Configuration in the Project

Under Signing & Capabilities in your project target, enable Incoming Connections (Server) and Outgoing Connections (Client).

Add the following to your app’s Info.plist:

<key>SUEnableInstallerLauncherService</key>
<true/>

<key>SUEnableDownloaderService</key>
<true/>

<key>SUFeedURL</key>
<string>https://your_github_username.github.io./your_repository_name/appcast.xml</string>

<key>SUPublicEDKey</key>
<string>your_previous_generated_public_Key</string>

Add keys to your app’s entitlements file:

<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
</array>

Integrating Sparkle with SwiftUI

When working with SwiftUI and looking to provide a seamless update experience, Sparkle comes in handy. Here’s how you can create an updater using Sparkle in a SwiftUI application:

Begin by importing the necessary modules.

import SwiftUI
import Sparkle

Create a CheckForUpdatesViewModel. This class observes and publishes when new updates can be checked by the user. The @Published property canCheckForUpdates reflects the availability of updates.

final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false

init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
}
}

The CheckForUpdatesView represents the UI for the "Check for Updates" menu item.

struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
private let updater: SPUUpdater

init(updater: SPUUpdater) {
self.updater = updater
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
}

var body: some View {
Button("Check for Updates…", action: updater.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}

The main structure of your SwiftUI app, MyApp, initializes the Sparkle updater and defines a command to introduce the "Check for Updates" option in the app's menu.

@main
struct MyApp: App {
private let updaterController: SPUStandardUpdaterController

init() {
updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
}

var body: some Scene {
WindowGroup {
}
.commands {
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updater: updaterController.updater)
}
}
}
}

Getting the App Update Ready

First, in Xcode, head to Product > Archive > Direct Distribution and ensure your app is notarized (it will take few minutes). Once this is complete, export the app and compress it into a ZIP file. This ZIP will act as your deliverable for updates.

Generating the appcast.xml

The appcast.xml file is where Sparkle checks to see if there are any updates for your app. To create this file, use the terminal and run the ./bin/generate_appcast tool followed by the path to your updates folder.

./bin/generate_appcast /path/to/your/updates_folder/

During its creation, delta files might appear in the updates folder. These files represent efficient, incremental updates. However, if you find them unnecessary, they can be easily ignored. For a deeper understanding of these elements, refer to the official appcast documentation.

Hosting Your Update

Navigate to GitHub Releases on your repository and upload your ZIP file.

To upload your ZIP file to GitHub:

  1. Open your repository on GitHub.
  2. Go to the “Releases” tab, which is typically located near the top of the page.
  3. Click on “Draft a new release” or “Create a new release.”
  4. In the release form, drag-and-drop your ZIP file or click “Choose a file” in the “Attach binaries for this release by dropping them here” section.
  5. Fill in other release details as needed, like the version tag and release name.
  6. Click “Publish release.”

After publishing, right-click on your ZIP file in the “Releases” section and select “Copy link address.” Make sure the links in your appcast.xml file match this copied URL to ensure Sparkle can access and download the update.

 <enclosure url="https://your/zip/url" ... />

Wrapping Up and Testing

After committing the appcast.xml in your GitHub repository, launch your app and test out Sparkle’s update mechanisms. If all goes smoothly, updates should be detected and installed seamlessly.

Note: It’s common for Xcode to throw an error if you run the app directly from it and then attempt an update using Sparkle. To avoid this, export your app as you did earlier and try the update from that standalone version.

Conclusion

With GitHub and Sparkle, updating your macOS app outside the store becomes much easier. If you’re curious to dive deeper, the official Sparkle documentation is a great resource. I trust this guide has helped you on your journey. Keep coding!

--

--