How to Access or Modify Application Preferences Programmatically in OpenSuperWhisper
Accessing or modifying application preferences programmatically in OpenSuperWhisper is done entirely through the AppPreferences.shared singleton, whose custom property wrappers read from and write to UserDefaults immediately on every access.
OpenSuperWhisper stores all user-configurable options in a centralized Swift singleton rather than scattering keys across view models. Because the AppPreferences class in OpenSuperWhisper/Utils/AppPreferences.swift relies on lightweight property wrappers, you can access or modify application preferences programmatically from any module or unit test without importing extra frameworks or handling threading concerns.
The AppPreferences Singleton and Property Wrappers
The heart of the system is AppPreferences, a singleton that exposes a static shared instance and lazily migrates legacy keys on initialization. Each preference is declared as a Swift property wrapped by one of two types defined in the same file:
- UserDefault — Persists a non-optional value to
UserDefaults.standardand returns a specifieddefaultValuewhen the key is missing. - OptionalUserDefault — Persists an optional value, where
nilremoves the key fromUserDefaults.
For example, the transcription language is declared as:
@UserDefault(key: "whisperLanguage", defaultValue: "en")
var whisperLanguage: String
Reading or assigning to whisperLanguage routes through the wrapper’s wrappedValue, which calls UserDefaults.standard.object(forKey:) on access and set(_:forKey:) on assignment.
Reading a Preference Programmatically
To retrieve a stored setting, read the property directly from AppPreferences.shared:
let currentLanguage = AppPreferences.shared.whisperLanguage
print("Current transcription language: \(currentLanguage)")
If the user has never changed the language, the wrapper returns the default fallback "en".
Modifying a Preference Programmatically
Changing a value is equally direct. Assigning to the singleton property updates the underlying UserDefaults entry instantly:
AppPreferences.shared.autoCopyToClipboard = true
AppPreferences.shared.modifierOnlyHotkey = "leftCommand"
No explicit save call is required; the property wrapper synchronizes the value on each didSet.
Working with Optional Preferences
Some settings have no sensible fallback and are therefore optional. The selectedWhisperModelPath property uses OptionalUserDefault<T>:
if let modelPath = AppPreferences.shared.selectedWhisperModelPath {
print("Using model at: \(modelPath)")
} else {
print("No Whisper model selected")
}
// Clear the selection by setting nil
AppPreferences.shared.selectedWhisperModelPath = nil
Storing nil removes the key from UserDefaults entirely.
Adding a New Preference
Extending the settings surface requires only two steps in OpenSuperWhisper/Utils/AppPreferences.swift:
- Declare a new wrapped property with a unique key and an appropriate default.
- Access the property anywhere in the app through
AppPreferences.shared.
// 1. Declaration inside AppPreferences.swift
@UserDefault(key: "useDarkMode", defaultValue: false)
var useDarkMode: Bool
// 2. Programmatic usage
AppPreferences.shared.useDarkMode = true
The wrapper automatically registers the key in UserDefaults and handles serialization for any PropertyList type.
Syncing Preferences with SwiftUI
The Settings UI keeps reactive state in OpenSuperWhisper/SettingsViewModel.swift. The view model mirrors AppPreferences into @Published properties and writes back on every change:
class SettingsViewModel: ObservableObject {
@Published var debugMode: Bool {
didSet { AppPreferences.shared.debugMode = debugMode }
}
init() {
self.debugMode = AppPreferences.shared.debugMode
}
}
This ensures the UserDefaults backing store remains the single source of truth while SwiftUI views stay responsive.
Key Files in the Preferences System
Three files form the complete pipeline from definition to UI:
OpenSuperWhisper/Utils/AppPreferences.swift— Defines the singleton, property wrappers, and default-value migration.OpenSuperWhisper/Settings.swift— Exposes a plain Swift struct snapshot for non-UI consumers that do not need observation.OpenSuperWhisper/SettingsViewModel.swift— Bridges the singleton to SwiftUI via@Publishedproperties.
Summary
AppPreferences.sharedinOpenSuperWhisper/Utils/AppPreferences.swiftis the single entry point for every setting.- UserDefault wraps non-optional keys with a guaranteed fallback, while OptionalUserDefault handles nilable settings.
- You can access or modify application preferences programmatically with standard dot syntax; changes persist to
UserDefaultsimmediately. - The
SettingsViewModelmirrors these values to keep the SwiftUI interface synchronized without duplicating state. - No special APIs, file-path manipulation, or threading logic is required.
Frequently Asked Questions
How do I read a preference if I do not know whether it has been set?
The UserDefault<T> wrapper stores a defaultValue inside its declaration, so reading AppPreferences.shared.whisperLanguage always yields a valid string. You never need to check UserDefaults directly for key existence.
Is it safe to change preferences from a background thread?
Yes. The property wrappers perform simple UserDefaults.standard get and set operations, and UserDefaults is thread-safe on macOS. You can assign values from any queue without additional synchronization.
Where is the data physically stored?
The wrappers delegate to UserDefaults.standard, which persists values in the app’s sandboxed preferences property list. You do not need to manage file paths or plist serialization yourself.
What is the difference between Settings.swift and AppPreferences.swift?
OpenSuperWhisper/Utils/AppPreferences.swift owns the persistent singleton and the property-wrapper definitions, whereas OpenSuperWhisper/Settings.swift provides a plain struct snapshot for non-UI code that only needs a one-time read of the current configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →