{"id":28531371,"url":"https://github.com/lessica/observableuserdefaults","last_synced_at":"2025-08-16T18:08:47.751Z","repository":{"id":51609728,"uuid":"357807199","full_name":"Lessica/ObservableUserDefaults","owner":"Lessica","description":"An easy-to-use UserDefaults extension.","archived":false,"fork":false,"pushed_at":"2021-04-18T03:29:22.000Z","size":8,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-07T12:42:27.244Z","etag":null,"topics":["cocoa","kvo","macos","swift","userdefaults"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lessica.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-14T07:04:04.000Z","updated_at":"2024-11-08T14:40:09.000Z","dependencies_parsed_at":"2022-08-22T13:11:03.501Z","dependency_job_id":null,"html_url":"https://github.com/Lessica/ObservableUserDefaults","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Lessica/ObservableUserDefaults","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lessica%2FObservableUserDefaults","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lessica%2FObservableUserDefaults/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lessica%2FObservableUserDefaults/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lessica%2FObservableUserDefaults/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lessica","download_url":"https://codeload.github.com/Lessica/ObservableUserDefaults/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lessica%2FObservableUserDefaults/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270749212,"owners_count":24638681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-16T02:00:11.002Z","response_time":91,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoa","kvo","macos","swift","userdefaults"],"created_at":"2025-06-09T15:10:24.258Z","updated_at":"2025-08-16T18:08:47.726Z","avatar_url":"https://github.com/Lessica.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ObservableUserDefaults\n\nOriginal: https://stackoverflow.com/a/54889233/5227717\n\nThese extensions make `UserDefaults` observable and easy to use.\n\n\n## What's wrong?\n\nSee documentation of `UserDefaults.didChangeNotification`\n\n\u003e ### Summary\n\u003e \n\u003e Posted when user defaults are changed within the current process.\n\u003e \n\u003e \n\u003e ### Declaration\n\u003e \n\u003e ```swift\n\u003e public class let didChangeNotification: NSNotification.Name\n\u003e ```\n\u003e \n\u003e \n\u003e ### Discussion\n\u003e \n\u003e This notification is posted on the thread that changes the user defaults. The notification object is the UserDefaults object. The notification **doesn't contain a userInfo dictionary**.\n\u003e This notification **isn't posted when changes are made outside the current process**, or when ubiquitous defaults change. You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.\n\u003e \n\n## Example \u0026 Usage\n\n\n### Define Keys\n\n```swift\n// IMPORTANT: DON'T use DOT `.` in key.\n// DOT `.` used to define `KeyPath` and this is what we don't need here.\nextension UserDefaults.Key {\n    static let AppleMomentumScrollSupported  : UserDefaults.Key  = \"AppleMomentumScrollSupported\"     // Bool\n    static let enableNetworkDiscovery        : UserDefaults.Key  = \"defaults:enableNetworkDiscovery\"  // Bool\n    static let usesDetailedToolTips          : UserDefaults.Key  = \"defaults:usesDetailedToolTips\"    // Bool\n    static let screenshotSavingPath          : UserDefaults.Key  = \"defaults:screenshotSavingPath\"    // String\n}\n```\n\n\n### Register Initial Values\n\n```swift\n// From Source\nvar initialValues: [UserDefaults.Key: Any?] = [\n    .enableNetworkDiscovery  : true,\n    .usesDetailedToolTips    : false,\n    .screenshotSavingPath    : FileManager.default\n        .urls(for: .picturesDirectory, in: .userDomainMask)\n        .first?.appendingPathComponent(\"JSTColorPicker\").path,\n    .pixelMatchAAColor       : NSColor.systemYellow,\n    .pixelMatchDiffColor     : NSColor.systemRed,\n]\n\n// From Property List\n(try?\n    PropertyListSerialization.propertyList(\n        from: Data(contentsOf: Bundle.main.url(forResource: \"InitialValues\", withExtension: \"plist\")!),\n        options: [],\n        format: nil\n    )\n    as? [String : Any?])?.forEach({ initialValues[UserDefaults.Key(rawValue: $0.key)] = $0.value })\n\nUserDefaults.standard.register(defaults: initialValues)\n```\n\n\n### Access Direct Values\n\n```swift\nlet discoveryEnabled: Bool = UserDefaults.standard[.enableNetworkDiscovery]\n\n// Access Optional Values\nlet savingPath: String? = UserDefaults.standard[.screenshotSavingPath]\nguard let savingPath: String = UserDefaults.standard[.screenshotSavingPath] else { return }\n```\n\n\n### Modify Direct Values\n\n```swift\n@IBAction func discoveryMenuItemTapped(_ sender: NSMenuItem) {\n    let enabled = sender.state == .on\n    sender.state = !enabled ? .on : .off\n    UserDefaults.standard[.enableNetworkDiscovery] = !enabled\n}\n```\n\n\n### Observe Changes of Single Value\n\n```swift\n// You must keep a reference to this object.\nlet observable = UserDefaults.standard.observe(key: .AppleMomentumScrollSupported) { (defaults, defaultKey, defaultValue: Bool) in\n    let momentumScrollSupported = defaultValue\n    // do something\n}\n```\n\n\n### Observe Changes of Multiple Values\n\n```swift\nclass MyClass {\n    private var observables           : [Observable]?\n    private var usesDetailedToolTips  : Bool = false\n\n    private func setupHandlers() {\n        observables = UserDefaults.standard.observe(\n            keys: [.AppleMomentumScrollSupported, .usesDetailedToolTips], \n            callback: { [weak self] in self?.applyDefaults($0, $1, $2) /* You must use weak self here! */ }\n        )\n    }\n\n    private func applyDefaults(_ defaults: UserDefaults, _ defaultKey: UserDefaults.Key, _ defaultValue: Any) {\n        if defaultKey == .usesDetailedToolTips, let toValue = defaultValue as? Bool {\n            if usesDetailedToolTips != toValue {\n                usesDetailedToolTips = toValue\n                // do something\n            }\n        }\n    }\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flessica%2Fobservableuserdefaults","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flessica%2Fobservableuserdefaults","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flessica%2Fobservableuserdefaults/lists"}