Skip to content

Instantly share code, notes, and snippets.

@helje5
Last active January 11, 2023 00:37
Show Gist options
  • Save helje5/835cc98dc0cb003e572ab7a23024d501 to your computer and use it in GitHub Desktop.
Save helje5/835cc98dc0cb003e572ab7a23024d501 to your computer and use it in GitHub Desktop.
How to hookup Sparkle in SwiftUI
//
// SparkleCommands.swift
// Past for iChat
//
// Created by Helge Heß on 08.04.21.
//
import SwiftUI
#if SPARKLE && canImport(Sparkle)
import Sparkle
/**
* Adds the "Check for Updates" menu if Sparkle is enabled (i.e. can be
* imported and the `SPARKLE` compilation condition is set.
*
* Using it in an app:
*
* @main
* struct PastApp: App { // https://zeezide.de/en/products/past/
* var body: some Scene {
* DocumentGroup(viewing: PastDocument.self) { file in
* ContentView(document: file.$document)
* }
* .commands {
* TextFormattingCommands()
* SparkleCommands() // Just drop it in here
* }
* }
* }
*
* One may want to make settings like `automaticallyChecksForUpdates` user
* configurable in the app preferences:
*
* @main
* struct PastApp: App { // https://zeezide.de/en/products/past/
*
* @AppStorage("autoUpdate") var doAutoUpdates = true
*
* var body: some Scene {
* DocumentGroup(viewing: PastDocument.self) { file in
* ContentView(document: file.$document)
* }
* .commands {
* TextFormattingCommands()
* SparkleCommands(
* automaticallyChecksForUpdates: doAutoUpdates
* )
* }
*
* Settings {
* YourSettingsView(doAutoUpdates: $doAutoUpdates)
* }
* }
* }
*/
struct SparkleCommands: Commands {
private var automaticallyChecksForUpdates : Bool
private var automaticallyDownloadsUpdates : Bool
private var updateCheckInterval : TimeInterval
private var userAgentString : String?
private var httpHeaders : [ String : String ]?
private var sendsSystemProfile : Bool
init(automaticallyChecksForUpdates : Bool = true,
automaticallyDownloadsUpdates : Bool = false,
sendsSystemProfile : Bool = false,
updateCheckInterval : TimeInterval = 24 * 60 * 60 /*daily*/,
userAgentString : String? = nil,
httpHeaders : [ String : String ]? = nil)
{
self.automaticallyChecksForUpdates = automaticallyChecksForUpdates
self.automaticallyDownloadsUpdates = automaticallyDownloadsUpdates
self.sendsSystemProfile = sendsSystemProfile
self.updateCheckInterval = updateCheckInterval
self.userAgentString = userAgentString
self.httpHeaders = httpHeaders
let me = self
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
SparkleCommands.update(with: me)
}
}
var body: some Commands {
CommandGroup(after: .appInfo) {
Button("Check for Updates …") {
SparkleCommands.updater?.checkForUpdates()
}
}
}
fileprivate static var updater : SPUUpdater?
private final class SparkleDelegate:
NSObject, SPUUpdaterDelegate, SPUStandardUserDriverDelegate {}
private static let delegate = SparkleDelegate()
private static func update(with configuration: SparkleCommands) {
func configure(updater: SPUUpdater) {
updater.automaticallyChecksForUpdates =
configuration.automaticallyChecksForUpdates
updater.automaticallyDownloadsUpdates =
configuration.automaticallyDownloadsUpdates
updater.sendsSystemProfile = configuration.sendsSystemProfile
updater.updateCheckInterval = configuration.updateCheckInterval
if let v = configuration.userAgentString { updater.userAgentString = v }
if let v = configuration.httpHeaders { updater.httpHeaders = v }
}
if let updater = SparkleCommands.updater {
configure(updater: updater)
}
else {
let updateDriver = SPUStandardUserDriver(hostBundle: .main,
delegate: delegate)
let updater = SPUUpdater(hostBundle: .main, applicationBundle: .main,
userDriver: updateDriver, delegate: delegate)
configure(updater: updater)
SparkleCommands.updater = updater
do {
try updater.start()
updater.checkForUpdatesInBackground()
}
catch {
print("ERROR: failed to start Sparkle updater:", error)
}
}
}
}
#else // No Sparkle
/**
* Sparkle is inactive, this will yield no commands.
*/
struct SparkleCommands: Commands {
let body = EmptyCommands()
}
#endif
@main
struct PastApp: App { // https://zeezide.de/en/products/past/
var body: some Scene {
DocumentGroup(viewing: PastDocument.self) { file in
ContentView(document: file.$document)
}
.commands {
TextFormattingCommands()
AboutCommands()
SparkleCommands() // Just drop it in here
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment