8

I would like to use the block-based KVO from Swift 4 to observe changes to a value in UserDefaults. I am able to do this for observing a key path for WKWebView's estimatedProgress but haven't been successful with UserDefaults because the provided key path isn't what it's looking for. Providing just a String isn't enough (Generic parameter 'Value' could not be inferred), prefixing it with \ isn't enough (Type of expression is ambiguous without more context). What's the correct way to create the KeyPath to observe a value in UserDefaults?

observerToken = UserDefaults.standard.observe("myvalue") { (object, change) in
    //...
}
Anh Pham
  • 2,108
  • 9
  • 18
  • 29
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • I think you will need to add an observer for UserDefaults did change notification and do whatever you need there – Leo Dabus Jun 08 '17 at 01:00

1 Answers1

6

Yes its possible.First of all you need to define keypath as

extension UserDefaults
{
    @objc dynamic var isRunningWWDC: Bool
    {
        get {
            return bool(forKey: "isRunningWWDC")
        }
        set {
            set(newValue, forKey: "isRunningWWDC")
        }
    }
}

And use that keypath for block based KVO as

var observerToken:NSKeyValueObservation?
observerToken = UserDefaults.standard.observe(\.isRunningWWDC, options:[.new,.old])
{ (object, change) in

    print("Change is \(object.isRunningWWDC)")

}
UserDefaults.standard.isRunningWWDC = true
Daniel
  • 1,473
  • 3
  • 33
  • 63
LC 웃
  • 18,888
  • 9
  • 57
  • 72
  • 2
    This is working for a new key I add, but I cannot get this to work for an existing key, even if I delete and reinstall the app. Have you seen that before? The code isn't any different, I'm going insane. – Jordan H Jun 16 '17 at 03:20
  • Have you tried setting options as `options:[.new,.old,.prior,.initial]` – LC 웃 Jun 16 '17 at 04:23
  • 2
    Yes so including `.initial` triggers the change block, but it's not triggered later on change. – Jordan H Jun 16 '17 at 04:58
  • 2
    After hard trying, i don't think its possible to apply KVO on UserDefaults to observe every changes. – LC 웃 Jun 16 '17 at 06:41
  • 2
    I had to also add the setter to the key path definition, and have my objects use that setter (and not set the underlying UserDefaults key). Then edits started showing up as well as the initial. – rcw3 Jun 25 '18 at 20:52
  • @rcw3 did you do it the way explained in this question? Did you maybe test it on the background? – Solomiya Aug 01 '18 at 10:46
  • Just noting that Apple recommended this approach in the WWDC video Introducing Multiple Windows on iPad at the 38:23 mark. The only diff I see is they only use `.initial` and they don't access the `object` or `change`, instead they get the current value from `UserDefaults`. Haven't tested it again to examine its behavior but I'll go ahead and accept this answer. – Jordan H Jul 27 '19 at 18:22
  • 4
    @JordanH I just experienced the same issue as you described above: observer was only triggered the first time. I fixed it by changing the name of the var in `dynamic var isRunningWWDC`. The var must exactly match the user default key! – themenace Sep 26 '19 at 20:45
  • What if your key path is not known at compile time and you still want to use the block based observe method? – malhal Jul 09 '20 at 11:54
  • @themenace Upvoted your comment, the var name indeed must be the exact same as the key, and that's also the case when using Combine-based observation instead of block-based. – Jordan H Sep 06 '21 at 18:15
  • @themenace Interesting how all the answers illustrating this method forget to mention that detail, even though it's the obvious way that this could possibly work at all. So what happens if I have reserved characters in my key? No observing for me... – Andreas Jul 24 '22 at 20:18