Contents

simonnickel/snap-settings-service

This package is part of the SNAP suite.

Setup

To support settings stored in iCloud (NSUbiquitousKeyValueStore) you have to add the iCloud Capability to the target and enable the Key-value storage checkbox.

Demo

The demo project shows an example usage of the SettingsService with settings in different scopes.

<img src="/screenshot.png" height="400">

How to use

Define your settings:

extension SettingsService.SettingDefinition {
	static let exampleNumber = SettingsService.Setting<Int>("ExampleNumber", in: .defaults, default: 1)
}

Read and write the settings:

let settings = SettingsService()
settings.set(.exampleNumber, to: 2)
let number = settings.get(.exampleNumber)

Use the binding, when you need it to update on changes (value(_:) is @MainActor):

struct MyView: View {
	let observableValue: SettingsService.Value<Int> = settings.value(.exampleNumber)
	var body: some View {
		Text("\(observableValue.value)")
		SomeView(binding: observableValue.binding)
	}
}

Scope

The SettingsService can be configured with a SettingsStore object for a Scope: .defaults, .ubiquitous, .custom(id:)

SettingsService.init(defaults: UserDefaults? = .standard, ubiquitous: NSUbiquitousKeyValueStore? = .default, storesForCustomScopes: [Scope : SettingsStore] = [:])

.defaults

Stored locally in UserDefaults.

// TODO: How to handle privacy requirements.

.ubiquitous

Stored in iCloud using NSUbiquitousKeyValueStore.

If iCloud is unavailable, NSUbiquitousKeyValueStore stores values locally as a fallback — SettingsService is not involved in that fallback. A warning is only logged if no store is registered for the scope at all.

To use NSUbiquitousKeyValueStore, you must distribute your app through the App Store or Mac App Store, and you must request the com.apple.developer.ubiquity-kvstore-identifier entitlement in your Xcode project.

NSUbiquitousKeyValueStore Documentation

(see Setup)

.custom(id:)

You can provide one or multiple custom stores that implement SettingsStore.

If there is no store registered for the scope, a warning is logged.

SettingsStore

UserDefaults and NSUbiquitousKeyValueStore are extended to conform to SettingsStore.

Custom

You can create a custom store by implementing SettingsStore.

public protocol SettingsStore {
	func get(_ key: SettingsService.StorageKey) -> Data?
	func set(_ key: SettingsService.StorageKey, to data: Data?)
}

Access Setting

Get & Set

let settings = SettingsService()
settings.set(.exampleNumber, to: 2)
let number = settings.get(.exampleNumber)

Observable Value

value(_:) is @MainActor.

struct MyView: View {
	let observableValue: SettingsService.Value<Int> = settings.value(.exampleNumber)
	var body: some View {
		Text("\(observableValue.value)")
		SomeView(binding: observableValue.binding)
	}
}

Publisher

The SettingsService provides a Combine publisher to receive updated values. If the setting is stored in the .ubiquitous scope, the publisher is updated on remote changes (NSUbiquitousKeyValueStore.didChangeExternallyNotification).

Environment

The SettingsService can be injected via a SwiftUI environment key (@Entry on EnvironmentValues).

Inject into the @Environment:

View().environment(\.serviceSettings, settings)

Read from @Environment:

@Environment(\.serviceSettings) private var settings

TODO

// TODO: App Groups? Access in Widget? // TODO: Handle iCloud not available. It does store locally and logs a warning, but should do something? // TODO: Compare with https://github.com/kylehughes/PersistentKeyValueKit // TODO: Compare with https://github.com/sindresorhus/Defaults

Package Metadata

Repository: simonnickel/snap-settings-service

Default branch: main

README: README.md