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.
extension AppSettingValues {
@Setting public var score: Int = 0
}
struct AppSettingsView: View {
@AppSetting(\.$score) var score
var body: some View {
Form {
TextField("Enter your score", value: $score, format: .number)
.textFieldStyle(.roundedBorder)
.padding()
Text("Your score was \(score).")
}
}
}Use the UserDefaultsStoreMock class in Previews:
#if DEBUG
import SettingsMock
#Preview {
AppSettingsView()
.environment(\.userDefaultsStore, UserDefaultsStoreMock.standard)
}
#endifNote:
AppSettingValuesis a mockable UserDefaults container already declared in the Settings library.
@main
struct MyApp: App {
init() {
AppSettingValues.prefix = "myapp_"
}
var body: some Scene {
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:
struct AppSettings {
@Setting static var username: 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_"
struct AppSettings {
@Setting static var username: 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)
for try await theme in AppSettings.$theme.stream {
print("Theme:", theme)
}struct UserProfile: Codable, Equatable {
let name: String
let age: Int
}
@Settings(prefix: "app_")
struct Settings {
@Setting(encoding: .json)
static var profile: UserProfile = .init(name: "Guest", age: 0)
}
Settings.profile = .init(name: "Alice", age: 30)@Settings(prefix: "app_")
struct Settings {
@Setting static var apiKey: String? // no default value!
}
Settings.apiKey = "secret123"
Settings.apiKey = nil // removes key from UserDefaultsSupport keys within a name space:
struct AppSettings {}
extension AppSettings { enum UserSettings {} }
extension AppSettings.UserSettings {
@Setting static var email: String?
}
print(AppSettings.UserSettings.$email.key) // "UserSettings::email"
AppSettings.UserSettings.email = "alice@example.com"Use explicit isolation if accessed across actors.
@Settings(prefix: "app_")
struct Settings {
@MainActor @Setting static var theme: 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")
struct SharedSettings {
@Setting static var syncEnabled: Bool = true
}
// Custom key
@Setting(name: "custom_key")
static var value: Int = 42
// Custom encoders/decoders
@Setting(encoder: JSONEncoder(), decoder: JSONDecoder())
static var customProfile: UserProfile = .init(name: "Custom", age: 0)
// Key-path observation
struct User: Codable, Equatable { var name: String; var age: Int }
@Settings(prefix: "app_")
struct Settings {
@Setting(encoding: .json)
static var user: User = .init(name: "Guest", age: 0)
}
for try await name in Settings.$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