How to observe changes in UserDefaults?
Here is possible solution
import Combine
// define key for observing
extension UserDefaults {
@objc dynamic var status: String {
get { string(forKey: "status") ?? "OFFLINE" }
set { setValue(newValue, forKey: "status") }
}
}
class Station: ObservableObject {
@Published var status: String = UserDefaults.standard.status {
didSet {
UserDefaults.standard.status = status
}
}
private var cancelable: AnyCancellable?
init() {
cancelable = UserDefaults.standard.publisher(for: \.status)
.sink(receiveValue: { [weak self] newValue in
guard let self = self else { return }
if newValue != self.status { // avoid cycling !!
self.status = newValue
}
})
}
}
Note: SwiftUI 2.0 allows you to use/observe UserDefaults
in view directly via AppStorage
, so if you need that status only in view, you can just use
struct SomeView: View {
@AppStorage("status") var status: String = "OFFLINE"
...
Trying to observe a string in UserDefaults, but struggling with compile errors
You can try this.
extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: "language") ?? "en" }
set { self.setValue(newValue, forKey: "language") }
}
}
class MyObject {
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observe(\.language, options: [.new], changeHandler: { (defaults, change) in
// your change logic here
})
}
deinit {
observer?.invalidate()
}
}
UPDATE
import Foundation
extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: #function) ?? "en" }
set { self.setValue(newValue, forKey: #function) }
}
}
class TopViewModel: NSObject {
let defaults = UserDefaults.standard
let languageKeyPath = #keyPath(UserDefaults.language)
override init() {
super.init()
defaults.addObserver(self, forKeyPath: languageKeyPath, options: .new, context: nil)
let language = defaults.language
print("initialLanguage: \(language)")
defaults.language = "en"
defaults.language = "fr"
}
deinit {
defaults.removeObserver(self, forKeyPath: languageKeyPath)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard (object as? UserDefaults) === defaults,
keyPath == languageKeyPath,
let change = change
else { return }
if let updatedLanguage = change[.newKey] as? String {
print("updatedLanguage : \(updatedLanguage)")
}
}
}
// Test code, run init to observe changes
let viewModel = TopViewModel()
How to use KVO for UserDefaults in Swift?
As of iOS 11 + Swift 4, the recommended way (according to SwiftLint) is using the block-based KVO API.
Example:
Let's say I have an integer value stored in my user defaults and it's called greetingsCount
.
First I need to extend UserDefaults
with a dynamic var
that has the same name as the user defaults key you want to observe:
extension UserDefaults {
@objc dynamic var greetingsCount: Int {
return integer(forKey: "greetingsCount")
}
}
This allows us to later on define the key path for observing, like this:
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in
// your change logic here
})
}
And never forget to clean up:
deinit {
observer?.invalidate()
}
How to detect changes in NSUserDefault?
You can post a notification whenever you call synchronize
[[NSNotificationCenter defaultCenter] postNotificationName:@"MyAppSettingsChanged" object:self userInfo:nil];
Then in your other class listen to the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppSettingsChanged:) name:@"MyAppSettingsChanged" object:nil];
-(void) onAppSettingsChanged:(NSNotification)notification
{
// settings changed
}
If you want, you can pass an NSDictionary
into userInfo when calling postNotificationName
that contains information like which settings have changed.
iOS NSUserDefaults Watching the change of values for a single key
Edit: viewDidUnload
is now deprecated, use dealloc
instead to removeObserver
This should work perfectly, I have just tested here.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:@"SomeKey"
options:NSKeyValueObservingOptionNew
context:NULL];
// Testing...
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"test" forKey:@"SomeKey"];
[defaults synchronize];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"SomeKey"];
}
- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
if([keyPath isEqual:@"SomeKey"])
{
NSLog(@"SomeKey change: %@", change);
}
}
Things you could test.
Put a break point in viewDidUnload and make sure the view isn't disappearing on you (since you are changing
SomeKey
from another viewController) If this is the case then maybe move your register/de-register code into init/dealloc depending on how your VC works.Use explicit KeyPath like
@"SomeKey"
not a substitution likeSOME_NSSTRING_VARIABLE
Related Topics
iOS Getting Location Updates When App Terminated Without Using Significantchange
Uitextfield in Uialertcontroller (Border, Backgroundcolor)
iOS Nested View Controllers View Inside Uiviewcontroller's View
Development Team Not Showing in Xcode
The App Delegate Must Implement the Window Property If It Wants to Use a Main Storyboard File Swift
How to Mimic Keyboard Animation on iOS 7 to Add "Done" Button to Numeric Keyboard
iOS 8 Keyboard Hides My Textview
How to Copy SQLite Database When Application Is Launched in iOS
Google Places Autocomplete on iOS - Can't Load Search Results - Try Again
How to Observe Changes in Userdefaults
Missing Marketing Icon Xcode Bug
Uikeyboardboundsuserinfokey Is Deprecated, What to Use Instead
Uiview Touch Event in Controller
Application Executable Is Missing a Required Architecture Armv6
Installing Ffmpeg iOS Libraries Armv7, Armv7S, I386 and Universal on MAC with 10.8