Type-safe, macro-powered Swift package for UserDefaults.
Add Settings to your Package.swift:
dependencies: [
.package(url: "https://github.com/couchdeveloper/Settings.git", from: "0.1.0")
]Or in Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/couchdeveloper/Settings.git - Select version: 0.1.0 or later
- 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
@Settings(prefix: "app_") // keys prefixed with "app_"
struct AppSettings {
@Setting static var username: String = "Guest"
@Setting(name: "colorScheme") static var theme: String = "light" // key = "colorScheme"
@Setting static var apiKey: String? // optional: no default
}
AppSettings.username = "Alice"
print(AppSettings.theme) // "light"struct SettingsView: View {
@State private var theme = AppSettings.theme
var body: some View {
Picker("Theme", selection: $theme) {
Text("Light").tag("light")
Text("Dark").tag("dark")
}
.onChange(of: theme) { _, newValue in
AppSettings.theme = newValue
}
}
}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 UserDefaultsextension AppSettings { enum UserSettings {} }
extension AppSettings.UserSettings {
@Setting static var email: String?
}
print(AppSettings.UserSettings.$email.key) // "app_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.1
- macOS 10.15+ / iOS 13.0+ / tvOS 13.0+ / watchOS 6.0+
dependencies: [
.package(url: "https://github.com/couchdeveloper/UserDefaults.git", from: "0.1.0")
]// 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)
}- Swift 6.1 or later
- iOS 17.0+ / macOS 15.0+ / tvOS 17.0+ / watchOS 10.0+
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Apache License (v2.0) - See LICENSE file for details