cocoabits / MASShortcut

Modern framework for managing global keyboard shortcuts compatible with Mac App Store. More details:
http://blog.shpakovski.com/2012/07/global-keyboard-shortcuts-in-cocoa.html
BSD 2-Clause "Simplified" License
1.52k stars 220 forks source link

MASShortcut.description when altering keycode #105

Closed kimaldis closed 7 years ago

kimaldis commented 7 years ago

I have a table, one row for each of a list of shortcuts held in my app including, amongst other things, a string which is the key combination that calls the shortcut. I'm populating the table using an array of shortcuts which is built when the shortcuts are instantiated earlier in the app. I pull this string from MASShortcut.description and that's fine until the app user changes the key combination for a shortcut, using a MASShortcutView. The description property doesn't change when the key combinations are changed.

Is there a way to retrieve string for the changed key combination or am I going to have to watch for changes and deal with it myself.

Thanks.

zoul commented 7 years ago

I don’t understand how would the automatic case work. You have some kind of a collection of shortcuts that you display using a table, right? Now if the user changes a shortcut using MASShortcutView, the contents of the collection change and you have to refresh the table. How could MASShortcut help you when it knows nothing about the collection? MASShortcut is an immutable class, there’s no event such as the key combination being changed. A different key combination requires a different MASShortcut instance. Or did I get your question wrong? Could be, it’s Friday evening after all :–)

kimaldis commented 7 years ago

I'm relatively new to Swift and Apple dev and wholly unfamiliar with ObjectiveC so it's almost certainly me that has it wrong, any day of the week. You're more or less right on the table method but I'd assumed wrongly on how the shortcut class worked. I'll need to go back and get my head around it properly. Thanks for your input, though. It was useful.

kimaldis commented 7 years ago

OK, I'm nearly there, I think. One more question:

I'm using MASShortcutBinder to store my shortcuts in userdefaults. Initially creating a set of shortcuts when the app starts:

let msShortcut = MASShortcut(keyCode: , modifierFlags:  )
MASShortcutBinder.shared().registerDefaultShortcuts([ userKey: msShortcut ]) 
MASShortcutBinder.shared().bindShortcut(withDefaultsKey:  userKey ) { // code }

And for each key storing the key combination in a collection, along with other data, which is used to populate the table. All well and good. Over in my Viewcontroller I have my table with a separate tab view for editing shortcuts. My user selects a row then switches to the edit tab if he wants to change the shortcut. I have a single MSShortcutView in this tab to change key combinations for the shortcut selected in the table and I use MSShortcutView.shortcutValueChange to catch changes and, update my collection with the new key combination and reload my table. I use MASShortcutView.associatedUserDefaultsKey to associate the shortcut key, which is stored in my collection, to the shortcut view when the user selects a row.

This works fine; my table shows any changed key combinations and my app reloads with the changed key combinations on the shortcuts but there's still a problem with any shortcuts the user has changed when the app is subsequently launched because I'm populating my collection with the default key combinations rather than the ones picked up from the user defaults store.

I could iterate through my collection, calling associatedUserDefaultsKey for each shortcut, since this triggers shortcutValueChange, but this seems a bit messy and I wondered if there was a better way.

thanks.

zoul commented 7 years ago

Since you already use NSUserDefaults for storage, can’t you also use it as the model, triggering all changes? In my app, all MASShortcutViews are bound to user defaults, and so are all parts of the app that depend on the shortcuts. When user changes a shortcut though the view, the bindings update the value stored in user defaults, which in turn – again, through bindings – updates the shortcuts throughout the app.

kimaldis commented 7 years ago

You mean retrieve the shortcut description from user defaults, rather than storing myself in the collection?

zoul commented 7 years ago

If you store the description independently on the shortcut itself, you’re definitely asking for trouble, as it’s easily possible for the two to get out of sync. I would never separate the description from the shortcut – if something displays a shortcut, it should get a real MASShortcut value and only convert it to the textual value at the last moment.

kimaldis commented 7 years ago

I do get that and I'm not. kind of.. I'm aware that my unfamiliarity with this might be causing some confusion so I'll have one more go before I stop annoying you. :-)

My app creates, registers and binds a set of shortcuts immediately it starts using MASShortcutBinder. My user is then presented with a table of shortcuts. I don't have a MASShortcutView associated with each shortcut, there will only ever be one and that's presented to my user when he double clicks on a table row. I'm assuming that normally there would be a MASShortcutview for each shortcut, my user would see the correct key combination in that and all would be fine. I only have one so that isn't working for me. I'm currently storing in a collection the shortcuts created back when the app first starts. This is obviously incorrect so what I'm asking is, is there a way to get the correct information to populate my collection?

Does that make sense?

zoul commented 7 years ago

Not annoying at all! Happy to help. Sorry for being daft.

Would it work for you to store not the shortcuts themselves in the collection, but their defaults keys? When displaying the collection you can then read the associated shortcut from defaults. A bit like this:

struct Item {
    let shortcutKey: String
    let userInfo1: …
    let userInfo2: …
}

// The syntax is off, but you get the idea
func displayItem(item: Item) -> String? {
    let defaults = UserDefaults.standard
    let shortcut = defaults.object(forKey: item.shortcutKey) as? MASShortcut
    return shortcut?.description
}
kimaldis commented 7 years ago

I'm getting nil from defaults.object but let me investigate at this end. It's probably my fault because it looks like what I want. Thanks.

kimaldis commented 7 years ago

It definitely returns nil. Are shortcuts stored as data rather than objects?.

My solution, however. It's roundabout but it works. I am the master of kludge.

 struct Item {
      let shortcutKey: String
      let userInfo1: …
      let userInfo2: …
  }
  @IBOutlet weak var ShortcutViewOutlet: MASShortcutView!

  func displayItem(item: Item) -> String? {
      ShortcutViewOutlet.associatedUserDefaultsKey = item.shortcutKey
      shortcut = ShortcutViewOutlet.shortcutValue
      return shortcut?.description
 }

Thanks again for your help. I have a much clearer understanding of this now.

zoul commented 7 years ago

My eyes, my eyes! :goberserk: :–) A shortcut can’t be saved to user defaults directly, it has to be serialized somehow. Take a look at MASDictionaryTransformer, it should be able to transform the value stored in user defaults back to a valid MASShortcut.

kimaldis commented 7 years ago

It doesn't appear to be as simple as:

let defaults = UserDefaults.standard
let data = defaults.data(forKey: userKey )

let shortcut = MASDictionaryTransformer().reverseTransformedValue( data )

or is it?

zoul commented 7 years ago

It’s close! There are two ways to store an MASShortcut in user defaults: NSData or NSDictionary. I like the dictionary approach better, since it makes the shortcut readable when accessing through the defaults CLI client. If you have them stored as dictionaries, you have to read them as dictionaries:

let defaults = UserDefaults.standard
let serializedShortcut = defaults.dictionary(forKey: …)
let shortcut = MASDictionaryTransformer().transformedValue(serializedShortcut)

I think that should work. If it doesn’t, take a look what serializedShortcut actually is.

kimaldis commented 7 years ago

Dictionarn() seemed to be the obvious place to start but it returns nil. data() wsa the only method that returned a non nil value.

zoul commented 7 years ago

In that case your shortcuts are serialized to NSData (using NSCoding) and you have to deserialize them using NSKeyedUnarchiver:

let defaults = UserDefaults.standard
let serializedShortcut = defaults.data(forKey: …)
let shortcut = NSKeyedUnarchiver.unarchiveObject(with: serializedShortcut)
kimaldis commented 7 years ago

That did it. I'm good to go now. Many thanks for your help and patience.

zoul commented 7 years ago

Happy to help!