swiftappkitkey-value-observingnsstatusitem

KVO on NSStatusItem.isVisible fires twice


I'm trying to use key value observation to determine when an NSStatusItem is dragged out of the menu bar by the user with the removalAllowed behavior. This is supported according to the docs:

Status items with this behavior allow interactive removal from the menu bar. Upon removal, the item’s isVisible property changes to false. This change is observable using key-value observation.

However, the callback function seems to fire twice whenever the isVisible property is changed. Here's a minimal example (assume statusItem and observer are variables that are retained for the lifetime of the app, e.g. on AppDelegate).

statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
statusItem.button!.image = NSImage(named: NSImage.addTemplateName)
statusItem.behavior = .removalAllowed

observer = statusItem.observe(\.isVisible, options: [.old, .new]) { object, change in
    print("oldValue: \(change.oldValue!) newValue: \(change.newValue!)")
}

If you drag the icon out of the menu bar, it will print the following:

oldValue: false newValue: true
oldValue: false newValue: true

I've looked through every property on the change object and as far as I can tell, they're all identical, so there'd be no easy way to discard the duplicate event. I've also messed with the prior option, which doesn't seem to help either.


Solution

  • A smart way to get rid of duplicates is to replace the KVO observer with a Combine publisher.

    import Combine
    
    var cancellable : AnyCancellable?
    
    cancellable = statusItem.publisher(for: \.isVisible)
        .removeDuplicates()
        .sink { value in
            print(value)
        }
    

    If you are interested in the old value you could add the Scan operator