This is an example of creating an updater in Sparkle 2 with Cocoa programmatically. It hooks up a menu item’s target/action for checking for updates.
import Cocoa
import Sparkle
@NSApplicationMain
@objc class AppDelegate: NSObject, NSApplicationDelegate {
// Hook up this menu item outlet in Interface Builder. The menu item's title is typically "Check for Updates…"
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
let updaterController: SPUStandardUpdaterController
override init() {
// If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later
// This is where you can also pass an updater delegate if you need one
updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
}
func applicationDidFinishLaunching(_ notification: Notification) {
// Hooking up the menu item's target/action to the standard updater controller does two things:
// 1. The menu item's action is set to perform a user-initiated check for new updates
// 2. The menu item is enabled and disabled by the updater controller depending on -[SPUUpdater canCheckForUpdates]
checkForUpdatesMenuItem.target = updaterController
checkForUpdatesMenuItem.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:))
}
}
For adding additional updater settings, you may also want to check out Adding a Settings UI in Cocoa.
This is an example of creating an updater in Sparkle 2 with SwiftUI. It creates a new menu item allowing users to check for new updates and ensures its disabled state is updated.
import SwiftUI
import Sparkle
// This view model class publishes when new updates can be checked by the user
final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false
init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
}
}
// This is the view for the Check for Updates menu item
// Note this intermediate view is necessary for the disabled state on the menu item to work properly before Monterey.
// See https://stackoverflow.com/questions/68553092/menu-not-updating-swiftui-bug for more info
struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
private let updater: SPUUpdater
init(updater: SPUUpdater) {
self.updater = updater
// Create our view model for our CheckForUpdatesView
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
}
var body: some View {
Button("Check for Updates…", action: updater.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}
@main
struct MyApp: App {
private let updaterController: SPUStandardUpdaterController
init() {
// If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later
// This is where you can also pass an updater delegate if you need one
updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
}
var body: some Scene {
WindowGroup {
}
.commands {
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updater: updaterController.updater)
}
}
}
}
For adding additional updater settings, you may also want to check out Adding Settings in SwiftUI.
This is an example for creating an updater in Sparkle 2 with Mac Catalyst. The code illustrated below is inside an AppKit bundle plug-in that is loaded by the Catalyst application using a shared PlugIn
protocol. This snippet is missing plenty of details including adding a menu item and inter-opt between the host application and plug-in.
import AppKit
import Foundation
import Sparkle
@objc class AppKitPlugin: NSObject, PlugIn {
let updaterController: SPUStandardUpdaterController
required override init() {
// We may want to defer starting the updater later, so we will pass false to startingUpdater
// This is where you can also pass an updater delegate if you need one
updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: nil, userDriverDelegate: nil)
}
// Called from the Catalyst app
func startUpdater() {
updaterController.startUpdater()
}
}
Note Sparkle’s standard user interface cannot display HTML release notes because macOS WebKit views cannot be used inside a Catalyst application. As of Sparkle 2.4 or later, you can use plain text release notes instead. For older versions of Sparkle, you can disable showing release notes by setting SUShowReleaseNotes to NO
. A more advanced approach for using HTML release notes using Sparkle 2 is instantiating SPUUpdater with your own SPUUserDriver and custom user interface, which may use an iOS WebKit view from the Catalyst side.
If you run into issues with loading the Sparkle framework from your plug-in at runtime, please refer to Add the Sparkle framework to your project where the setup guide talks about having the Runpath Search Paths set to @loader_path/../Frameworks
and using an Apple Development
certificate for development to satisfy Library Validation. This applies to the setup for the plug-in target as well.
This is an example of creating an updater in Sparkle 2 with Qt and uses Key-Value Observing (KVO) to ensure the check for update’s menu item state is updated. This example can also be used as reference when integrating Sparkle into other non-Apple provided toolkits.
// File: updater.h
// Description: Header interface for your application's updater. Implementation could differ by platform.
#ifndef UPDATER_H
#define UPDATER_H
#include <QObject>
class QAction;
#ifdef __OBJC__
@class AppUpdaterDelegate;
#endif
class Updater : public QObject
{
Q_OBJECT
public:
// Caller example from a QMainWindow subclass:
/*
QAction *updaterAction = new QAction(tr("&Check for Updates…"), this);
updaterAction->setMenuRole(QAction::ApplicationSpecificRole);
QMenu *updaterMenu = menuBar()->addMenu("&Updater");
updaterMenu->addAction(updaterAction);
Updater *updater = new Updater(updaterAction);
*/
// This constructor initializes the updater,
// and takes care of connecting the check for updates action and updating its enabled state
// Note: this must be called on the main thread
Updater(QAction *checkForUpdatesAction);
private slots:
void checkForUpdates();
private:
#ifdef __OBJC__
// Expose ObjC type only to updater_sparkle.mm. This allows ARC to properly track its lifetime.
AppUpdaterDelegate *_updaterDelegate;
#else
void *_updaterDelegate;
#endif
};
#endif
// File: updater_sparkle.mm
// Description: macOS and Sparkle implementation for the application's updater
// Notes: Compile updater_sparkle.mm with -fobjc-arc because this file assumes Automatic Reference Counting (ARC) is enabled
#include "updater.h"
#include <qaction.h>
#import <Sparkle/Sparkle.h>
// An updater delegate class that mainly takes care of updating the check for updates menu item's state
// This class can also be used to implement other updater delegate methods
@interface AppUpdaterDelegate : NSObject <SPUUpdaterDelegate>
@property (nonatomic) SPUStandardUpdaterController *updaterController;
@end
@implementation AppUpdaterDelegate
- (void)observeCanCheckForUpdatesWithAction:(QAction *)action
{
[_updaterController.updater addObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates)) options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:(void *)action];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
if ([keyPath isEqualToString:NSStringFromSelector(@selector(canCheckForUpdates))]) {
QAction *menuAction = (QAction *)context;
menuAction->setEnabled(_updaterController.updater.canCheckForUpdates);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
@autoreleasepool {
[_updaterController.updater removeObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))];
}
}
@end
// Creates and starts the updater. There's nothing else required.
Updater::Updater(QAction *checkForUpdatesAction)
{
@autoreleasepool {
_updaterDelegate = [[AppUpdaterDelegate alloc] init];
_updaterDelegate.updaterController = [[SPUStandardUpdaterController alloc] initWithStartingUpdater:YES updaterDelegate:_updaterDelegate userDriverDelegate:nil];
connect(checkForUpdatesAction, &QAction::triggered, this, &Updater::checkForUpdates);
[_updaterDelegate observeCanCheckForUpdatesWithAction:checkForUpdatesAction];
}
}
// Called when the user checks for updates
void Updater::checkForUpdates()
{
@autoreleasepool {
[_updaterDelegate.updaterController checkForUpdates:nil];
}
}
Specific steps on setting up an application and incorporating a framework using an external build system are outside the scope of this document. However, on the lower level you will need to at least:
updater_sparkle.mm
to source files to compile on macOS using -fobjc-arc
compile flags and add updater.h
to your project too-framework Sparkle
)Sparkle.framework
(-F/path/to/dir/
)-Wl,-rpath,@loader_path
). Use otool -l <executable-path>
and otool -L <executable-path>
to ensure all paths your application references are self-contained or provided from the macOS system.Sparkle.framework
into your built application bundle in Contents/Frameworks/
and make sure symlinks are properly preservedCFBundleIdentifier
, CFBundleVersion
, and CFBundleShortVersionString
keys in your application’s Info.plistSUFeedURL
and SUPublicEDKey
keys in your app’s Info.plist
described in later documentation sections-mmacosx-version-min=version
flag and LSMinimumSystemVersion
Info.plist key) and build architectures (e.g. -arch arm64 -arch x86_64
)Build systems may provide higher-level constructs to set these details. For example, cmake
’s find_library
command on a framework will automatically add -framework A
and -F<fullPath>
and cmake
’s MACOSX_BUNDLE_INFO_PLIST
property can be used to provide custom Info.plist keys.
For adding additional updater settings, you may also want to check out Adding Settings in Qt.
In Sparkle 2 you can also choose to instantiate and use SPUUpdater directly instead of the SPUStandardUpdaterController wrapper if you need more control over your user interface or what bundle to update.
If you use another UI toolkit, these are the relevant APIs in Sparkle 2 for checking for updates and handling menu item validation:
If you are using Sparkle 1, you will need to use these APIs:
+[SUUpdater sharedUpdater]
for creating and starting the updater automatically-[SUUpdater checkForUpdates:]
for the user to check for updates-[SUUpdater validateMenuItem:]
for menu item validationThis section only applies if you plan to use additional updater APIs that are not a part of the Core API methods listed above.
Prefer to set the updater’s initial properties in your bundle’s Info.plist as described in Customizing Sparkle. This includes properties like the updater’s feed URL and its update checking behavior. Only configuring the feed URL and signing keys (via SUFeedURL
and SUPublicEDKey
) in your bundle’s Info.plist is strongly recommended, which are described in later documentation sections.
Only set SPUUpdater (or SUUpdater
for Sparkle 1) properties, related to update checking, programatically when the user wants to make updater setting changes, otherwise you may be ignoring the user’s preferences and resetting the updater’s cycle unnecessarily. Similarly, developers shouldn’t maintain their own set of user defaults on top of SPUUpdater (or SUUpdater
) properties that are documented to already be backed by NSUserDefaults
from user setting changes. Please also refer to Adding a Settings UI for examples.
If you want to switch to an alternative feed at runtime, please check our section on setting the feed programmatically first on the recommended API usage (which is not through -setFeedURL:
).
Avoid forcing a manual -checkForUpdatesInBackground unless your application requires this check at specific points of time. If used incorrectly, this can interfere with Sparkle’s default behavior for asking the user’s permission to check for updates automatically, and for Sparkle calling this method periodically without probing too often. If you do require a manual background update check, either ensure -automaticallyChecksForUpdates is already YES
(but don’t set the property yourself in this context) or set SUEnableAutomaticChecks
Info.plist key described in Customizing Sparkle to explicitly opt out of asking the user permission to check for updates automatically. Finally, note that by default a new update that is found from a background check is not guaranteed to be shown to the user immediately (due to poor connectivity, downloading/installing an update silently in the background, or waiting until a more opportune time to alert the user). As of Sparkle 2.4 or later, if you want to trigger a background update check after the user has changed the allowed channels or feed, use -[SPUUpdater resetUpdateCycleAfterShortDelay] or -[SPUUpdater resetUpdateCycle] instead.