Revamp and improve SwiftUI programmatic samples (#168)

We provide a more sound SwiftUI sample and also show how to add user settings to a SwiftUI app.

Also rename Preferences to Settings in across the documentation.
This commit is contained in:
Zorg
2022-09-10 08:25:21 -07:00
committed by GitHub
parent 522a16d770
commit 7169798027
4 changed files with 99 additions and 38 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ Here are the main routes by which you can bend Sparkle's behavior to your will:
| `SUBundleName` | String | Optional alternative bundle display name. For example, if your bundle name already has a version number appended to it, setting this may help smooth out certain messages, e.g. "MyApp 3 4.0 is now available" vs "MyApp 4.0 is now available". |
| `SUDefaultsDomain` | String | Optional alternative `NSUserDefaults` domain name if you don't want to use the standard user defaults, for example when accessing preferences from an App Group suite. |
| `SUEnableJavaScript` | Boolean | Default: `NO`. Set this to `YES` if you want to allow JavaScript in your release notes. |
| `SURelaunchHostBundle` | Boolean | Default: `NO`. For plug-ins in Sparkle 2, set this to `YES` to re-launch the host targetted bundle instead of the application bundle. For example, this can be used to re-open a System Preferences prefpane after an update has been installed.
| `SURelaunchHostBundle` | Boolean | Default: `NO`. For plug-ins in Sparkle 2, set this to `YES` to re-launch the host targetted bundle instead of the application bundle. For example, this can be used to re-open a System Settings prefpane after an update has been installed.
### Sandboxing Settings
+1 -1
View File
@@ -190,7 +190,7 @@ That's it! You're done! You don't have to do any more. But you might want to:
* [Read more on publishing an update](/documentation/publishing/#publishing-an-update)
* [Customize Sparkle's settings and behavior](/documentation/customization/) for your product.
* [Add update settings](/documentation/preferences-ui/) to your preferences panel.
* [Add update settings](/documentation/preferences-ui/) to your settings panel.
* [Add binary delta updates](/documentation/delta-updates/) to your application.
* [Add gentle update reminders](/documentation/gentle-reminders) for your application.
* [Learn about gathering anonymous statistics about your users' systems](/documentation/system-profiling/).
+65 -13
View File
@@ -1,13 +1,14 @@
---
layout: documentation
id: documentation
title: Adding a Preferences UI
title: Adding a Settings UI
---
## Adding a Preferences UI
### Sparkle 2
## Sparkle 2
These instructions below are for adding preferences in a Cocoa nib and assume you have a Preferences nib with its `File's Owner` being set to your Preferences controller class (e.g, a `NSWindowController` subclass). The controller class should have an `updater` property set to your application's `SPUUpdater`, which may be passed from your application's delegate.
### Adding a Settings UI in Cocoa
These instructions below are for adding settings in a Cocoa nib and assume you have a Settings nib with its `File's Owner` being set to your Settings controller class (e.g, a `NSWindowController` subclass). The controller class should have an `updater` property set to your application's `SPUUpdater`, which may be passed from your application's delegate.
#### Enable automatic checking
@@ -25,24 +26,75 @@ These instructions below are for adding preferences in a Cocoa nib and assume yo
* Check the Bind to: check button, choose `File's Owner` from the popup, and set the Model Key Path to `updater.updateCheckInterval`.
* Select Enabled, check the Bind to: check button, choose `File's Owner` from the popup, and set the Model Key Path to `updater.automaticallyChecksForUpdates`.
#### Other preferences
#### Other settings
Follow directions similar to [Enable automatic checking](#enable-automatic-checking) to bind a check button to `updater.sendsSystemProfile` or `updater.automaticallyDownloadsUpdates`. See [customization](/documentation/customization/#infoplist-settings) for details on the available keys.
### Adding Settings in SwiftUI
This is a continuation from [Creating an Updater in SwiftUI](/documentation/programmatic-setup#create-an-updater-in-swiftui).
Create a new view for the updater settings:
```swift
// This is the view for our updater settings
// It manages local state for checking for updates and automatically downloading updates
// Upon user changes to these, the updater's properties are set. These are backed by NSUserDefaults.
// Note the updater properties should *only* be set when the user changes the state.
struct UpdaterSettingsView: View {
private let updater: SPUUpdater
@State private var automaticallyChecksForUpdates: Bool
@State private var automaticallyDownloadsUpdates: Bool
init(updater: SPUUpdater) {
self.updater = updater
self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates
self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates
}
var body: some View {
VStack {
Toggle("Automatically check for updates", isOn: $automaticallyChecksForUpdates)
.onChange(of: automaticallyChecksForUpdates) { newValue in
updater.automaticallyChecksForUpdates = newValue
}
Toggle("Automatically download updates", isOn: $automaticallyDownloadsUpdates)
.disabled(!automaticallyChecksForUpdates)
.onChange(of: automaticallyDownloadsUpdates) { newValue in
updater.automaticallyDownloadsUpdates = newValue
}
}.padding()
}
}
```
Then add these settings to your `App`'s `body`:
```swift
var body: some Scene {
/* ... */
Settings {
UpdaterSettingsView(updater: updaterController.updater)
}
}
```
---
### Sparkle 1
## Sparkle 1
These instructions below are for adding preferences in a Cocoa nib and assume you have a Preferences nib with its `File's Owner` being set to your Preferences controller class (e.g, a `NSWindowController` subclass). The controller class should have an `updater` property set to your application's `SUUpdater`, which may be passed from your application's delegate.
These instructions below are for adding settings in a Cocoa nib and assume you have a Settings nib with its `File's Owner` being set to your Settings controller class (e.g, a `NSWindowController` subclass). The controller class should have an `updater` property set to your application's `SUUpdater`, which may be passed from your application's delegate.
#### Enable automatic checking
### Enable automatic checking
* Drag a check button from the Library to your document.
* Set the title to something like "Automatically check for updates".
* Select the check button, and under the Bindings tab, select Value.
* Check the Bind to: check button, choose `File's Owner` from the popup, and set the Model Key Path to `updater.automaticallyChecksForUpdates`.
#### Update check interval
### Update check interval
* Drag a popup button from the Library to your document.
* Set the titles of the menu items to e.g. "Hourly", "Daily", "Weekly", "Monthly".
@@ -51,13 +103,13 @@ These instructions below are for adding preferences in a Cocoa nib and assume yo
* Check the Bind to: check button, choose `File's Owner` from the popup, and set the Model Key Path to `updater.updateCheckInterval`.
* Select Enabled, check the Bind to: check button, choose `File's Owner` from the popup, and set the Model Key Path to `updater.automaticallyChecksForUpdates`.
#### Other preferences
### Other settings
Follow directions similar to [Enable automatic checking](#enable-automatic-checking). to bind a check button to `sendsSystemProfile` or `automaticallyDownloadsUpdates`. See [customization](/documentation/customization/#infoplist-settings) for details on the available keys.
#### Preferences for non-app bundles
### Settings for non-app bundles
These directions do not work for non-app bundles, as the updater you add to the nib will be the `sharedUpdater` for the application bundle. To be able to bind to the updater for your bundle, you can add the following accessor to your preferences controller (the owner of the nib):
These directions do not work for non-app bundles, as the updater you add to the nib will be the `sharedUpdater` for the application bundle. To be able to bind to the updater for your bundle, you can add the following accessor to your settings controller (the owner of the nib):
```objc
- (SUUpdater *)updater {
@@ -69,6 +121,6 @@ Then just bind the controls to the File's Owner, and start the Model Key Path wi
---
### Watch out for preference caching
### Watch out for Preference Caching
macOS caches plist files in `~/Library/Preferences`, so don't edit them directly. If you want to tweak these files for testing (e.g. change last update check date), use the `defaults` command.
+32 -23
View File
@@ -35,6 +35,8 @@ import Sparkle
}
```
For adding additional updater settings, you may also want to check out [Adding a Settings UI in Cocoa](/documentation/preferences-ui#add-a-settings-ui-in-cocoa).
## Create an Updater in SwiftUI
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.
@@ -43,53 +45,60 @@ This is an example of creating an updater in Sparkle 2 with SwiftUI. It creates
import SwiftUI
import Sparkle
// This view model class manages Sparkle's updater and publishes when new updates are allowed to be checked
final class UpdaterViewModel: ObservableObject {
private let updaterController: SPUStandardUpdaterController
// This view model class publishes when new updates can be checked by the user
final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false
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)
updaterController.updater.publisher(for: \.canCheckForUpdates)
init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
}
func checkForUpdates() {
updaterController.checkForUpdates(nil)
}
}
// This additional view is needed 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 information
// 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 var updaterViewModel: UpdaterViewModel
@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: updaterViewModel.checkForUpdates)
.disabled(!updaterViewModel.canCheckForUpdates)
Button("Check for Updates…", action: updater.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}
@main
struct MyApp: App {
@StateObject var updaterViewModel = UpdaterViewModel()
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(updaterViewModel: updaterViewModel)
CheckForUpdatesView(updater: updaterController.updater)
}
}
}
}
```
For adding additional updater settings, you may also want to check out [Add Settings in SwiftUI](/documentation/preferences-ui#add-settings-in-swiftui).
## Create an Updater in Mac Catalyst
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.
@@ -275,7 +284,7 @@ This section only applies if you plan to use additional updater APIs that are no
Prefer to set the updater's initial properties in your bundle's Info.plist as described in [Customizing Sparkle](/documentation/customization/). 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](/documentation/#3-segue-for-security-concerns).
Only set [SPUUpdater](/documentation/api-reference/Classes/SPUUpdater.html) (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](/documentation/api-reference/Classes/SPUUpdater.html) (or `SUUpdater`) properties that are documented to already be backed by `NSUserDefaults` from user setting changes.
Only set [SPUUpdater](/documentation/api-reference/Classes/SPUUpdater.html) (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](/documentation/api-reference/Classes/SPUUpdater.html) (or `SUUpdater`) properties that are documented to already be backed by `NSUserDefaults` from user setting changes. Please also refer to [Adding a Settings UI](/documentation/preferences-ui) for examples.
If you want to switch to an alternative feed at runtime, please check our section on [setting the feed programmatically](/documentation/publishing#setting-the-feed-programmatically) first on the recommended API usage (which is not through `-setFeedURL:`).