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 creating your own 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 use SPUStandardUpdaterController if you are using Sparkle’s standard user interface and updating your own app bundle. Otherwise, you can choose to just use the core SPUUpdater. The updater can be accessed from SPUStandardUpdaterController via the updater property.
If you use another UI toolkit, these are the relevant APIs in Sparkle 2 for checking for updates and handling menu item validation:
This 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 your updater’s initial configuration 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. Besides configuring the feed URL and signing keys (via SUFeedURL and SUPublicEDKey), customization of settings is opt-in.
Only set SPUUpdater runtime 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 properties that are documented to already be backed by NSUserDefaults from user setting changes. Please also refer to Adding a Settings UI for examples.
Avoid forcing a manual call to -[SPUUpdater checkForUpdatesInBackground]. By default, Sparkle calls this method automatically for you on a scheduled basis (the default is once every 24 hours). If you want to additionally force an update check on every app launch though, only call this method immediately after the updater is started, and only call this method if the user hasn’t disabled automatic checking of updates by ensuring -[SPUUpdater automaticallyChecksForUpdates] reads as YES. Invoking checkForUpdatesInBackground manually at later points can interfere with Sparkle’s update scheduler in unexpected ways.
If you want to trigger a background update check after the user has changed the allowed channels or feed in your app’s settings, use -[SPUUpdater resetUpdateCycleAfterShortDelay] or -[SPUUpdater resetUpdateCycle].