Type-safe, macro-powered Swift package for UserDefaults.
- Type-safe — Compile-time type checking with generated keys
- No boilerplate —
@Settingsand@Settinggenerate accessors and metadata - UserDefaults-compatible — Integrates with existing suites without migration
- Codable-first — Encode any
Codabletype via JSON or property list - Native storage — Stores property list–compatible types directly (String, Int, Bool, Date, Data, URL, Array, Dictionary)
- Observable — Built-in
Combinepublishers andAsyncSequencestreams - Customizable — Namespaced keys and pluggable encoders/decoders
import Settings
import SwiftUI
// Special mockable Settings container.
// Uses UserDefaults.standard per default.
extensionAppSettingValues{@Settingpublicvarscore:Int=0}structAppSettingsView:View{@AppSetting(\.$score)varscorevarbody:someView{Form{TextField("Enter your score", value: $score, format:.number).textFieldStyle(.roundedBorder).padding()Text("Your score was \(score).")}}}Use the UserDefaultsStoreMock class in Previews:
#if DEBUGimport SettingsMock
#Preview {AppSettingsView().environment(\.userDefaultsStore,UserDefaultsStoreMock.standard)}#endifNote:
AppSettingValuesis a mockable UserDefaults container already declared in the Settings library.
@mainstructMyApp:App{init(){AppSettingValues.prefix ="myapp_"}varbody:someScene{WindowGroup{MainView()}}}Note: When the prefix is not customized, Settings library will use the bundle identifier from the main bundle to derive a unique prefix for every key.
You do not need to put the user settings into a type annotated with the @Settings macro. You also can declare or use any regular struct, class or enum as your UserDefaults container:
structAppSettings{@Settingstaticvarusername:String="Guest"}This will store and read from the setting in Foundation's UserDefaults.standard.
If you require more control, like having key prefixes, using your own UserDefaults suite, or if you want to mock Foundation UserDefaults in Previews or elsewhere, use the @Settings macro:
@Settings(prefix:"app_") // keys prefixed with "app_"
structAppSettings{@Settingstaticvarusername:String="Guest"}Access metadata and observe changes:
// Key
print(AppSettings.$username.key) // "app_username"
print(AppSettings.$theme.key) // "colorScheme"
// Reset
AppSettings.$theme.reset()
// Observe (Combine)
AppSettings.$theme.publisher.sink{print("Theme:", $0)}
// Observe (AsyncSequence)
fortryawaitthemeinAppSettings.$theme.stream {print("Theme:", theme)}structUserProfile:Codable,Equatable{letname:Stringletage:Int}@Settings(prefix:"app_")structSettings{@Setting(encoding:.json)staticvarprofile:UserProfile=.init(name:"Guest", age:0)}Settings.profile =.init(name:"Alice", age:30)@Settings(prefix:"app_")structSettings{@SettingstaticvarapiKey:String? // no default value!
}Settings.apiKey ="secret123"Settings.apiKey =nil // removes key from UserDefaultsSupport keys within a name space:
structAppSettings{}extensionAppSettings{enumUserSettings{}}extensionAppSettings.UserSettings{@Settingstaticvaremail:String?}print(AppSettings.UserSettings.$email.key) // "UserSettings::email"
AppSettings.UserSettings.email ="alice@example.com"Use explicit isolation if accessed across actors.
@Settings(prefix:"app_")structSettings{@MainActor@Settingstaticvartheme:String="light"}- Swift 6.2
- macOS 10.15+ / iOS 17.0+ / tvOS 17.0+ / watchOS 10.0+
Add Settings to your Package.swift:
dependencies:[.package(url:"https://github.com/couchdeveloper/Settings.git", from:"0.5.0")]Or in Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/couchdeveloper/Settings.git - Select version: 0.5.0 or later
// Suite (App Group)
@Settings(prefix:"shared_", suiteName:"group.com.myapp")structSharedSettings{@SettingstaticvarsyncEnabled:Bool=true}
// Custom key
@Setting(name:"custom_key")staticvarvalue:Int=42
// Custom encoders/decoders
@Setting(encoder:JSONEncoder(), decoder:JSONDecoder())staticvarcustomProfile:UserProfile=.init(name:"Custom", age:0)
// Key-path observation
structUser:Codable,Equatable{varname:String; varage:Int}@Settings(prefix:"app_")structSettings{@Setting(encoding:.json)staticvaruser:User=.init(name:"Guest", age:0)}fortryawaitnameinSettings.$user.stream(for: \.name){print("Name:", name)}Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Apache License (v2.0) - See LICENSE file for details