19

I have the following code aiming to catch the event of a NSUserDefaults value changing for a particular key.

 [[NSUserDefaults standardUserDefaults] addObserver:self
                                    forKeyPath:SOME_NSSTRING_VARIABLE
                                       options:NSKeyValueObservingOptionNew
                                       context:NULL];

 - (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{NSLog (@"Changed for key %@", keyPath); }

But the observeValueForKeyPath is never getting called. I even tried replacing the SOME_NSSTRING_VARIABLE too with a string as mentioned in Observing value changes to an NSUserDefaults key but it has not helped.

Update: I am changing the NSUserDefaults from a tabview. The above code to monitor changes is in a different tab of the same tabviewcontroller. In the tab where I monitor for the changes (the tab where above code exists), if I add a :

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//   NSLog(@"viewillappear");
NSUserDefaults *FUDefaults = [NSUserDefaults standardUserDefaults];
NSLog(@"The username obtained is: %@", [FUDefaults objectForKey:SOME_NSSTRING_VARIABLE]);
}

the updated NSUserDefaults value is obtained correctly, but the observeValueForKeyPath was never called.

Community
  • 1
  • 1
Sankar
  • 6,192
  • 12
  • 65
  • 89
  • Please post the code where you change the variable too – trapper May 28 '12 at 12:16
  • Also I take it you have `[[NSUserDefaults standardUserDefaults] addObserver...` inside `viewDidLoad` ? – trapper May 28 '12 at 12:20
  • NSUserDefaults *FDefaults = [NSUserDefaults standardUserDefaults]; [FDefaults setObject:userName.text forKey:SOME_NSSTRING_VARIABLE]; [[NSUserDefaults standardUserDefaults] synchronize]; – Sankar May 28 '12 at 12:24
  • Yes, I have the addObserver... inside the viewDidLoad function only – Sankar May 28 '12 at 12:25
  • After struggling with the same issue for one whole day: Make sure `SOME_NSSTRING_VARIABLE` does not contain any dots, since `keyPath` should not be namespaced in order to do KVO. – Yunus Nedim Mehel May 15 '19 at 09:49

4 Answers4

28

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 like SOME_NSSTRING_VARIABLE

trapper
  • 11,716
  • 7
  • 38
  • 82
10

Swift 3 Version:

override func viewDidLoad() {
    super.viewDidLoad()
    UserDefaults.standard.addObserver(self, forKeyPath: "keyPath", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "keyPath" {
        //Do something
    }
}

deinit {
    UserDefaults.standard.removeObserver(self, forKeyPath: "keyPath")
}
christopher.online
  • 2,614
  • 3
  • 28
  • 52
2

Swift Version:

func setUserDefaultsListener(){
    NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "keyPath", options: NSKeyValueObservingOptions.New, context: nil)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "keyPath" {
        //Do something
    }
}

deinit {
    NSUserDefaults.standardUserDefaults().removeObserver(self, forKeyPath: "keyPath")
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
fede1608
  • 2,808
  • 1
  • 16
  • 17
2

Swift 5 Version:

extension UserDefaults {
    @objc dynamic var keyPath: Int {
        return integer(forKey: "keyPath")
    }
}

HINT: make sure the var has exactly the same as the keyPath

and where you use the observer do:

var observer: NSKeyValueObservation?

override func viewDidLoad() {
    super.viewDidLoad()
    observer = UserDefaults.standard.observe(\.keyPath, options: [.initial, .new], changeHandler: { (defaults, change) in
        //Do something
    })
}

deinit {
    observer?.invalidate()
}
themenace
  • 2,601
  • 2
  • 20
  • 33