Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Possible to subclass UIView and redefine keyCommands property?
-
The goal is to support modifier keys from external physical keyboard. According to the apple dev document, it requires the custom view to redefine the
keyCommands
property.Is this something possible with the beta
ctypes
andobjc_utils
modules?Any help is appreciated!
-
Yes, it's possible, I can post an example here when the next beta is out (some things in the next build make this a little easier).
-
Wow awesome! I posted the question but was prepared to be told No. Can't wait to see the example. This new beta is getting really exciting! Thanks @omz
-
As promised, here's a minimal demo of a view that defines a hardware keyboard shortcut – note that this will only work with the latest beta (#160023).
from objc_util import * from random import random import ui UIKeyCommand = ObjCClass('UIKeyCommand') def keyCommands(_self, _cmd): cmd_key_flag = (1<<20) key_command = UIKeyCommand.keyCommandWithInput_modifierFlags_action_('R', cmd_key_flag, 'keyCommandAction') commands = ns([key_command]) return commands.ptr def canBecomeFirstResponder(_self, _cmd): return True def keyCommandAction(_self, _cmd): self = ObjCInstance(_self) r, g, b = random(), random(), random() self.setBackgroundColor_(UIColor.colorWithRed_green_blue_alpha_(r, g, b, 1)) KeyCommandsView = create_objc_class('KeyCommandsView', UIView, [keyCommands, canBecomeFirstResponder, keyCommandAction]) @on_main_thread def main(): main_view = ui.View(frame=(0, 0, 400, 400)) main_view.name = 'Key Commands Demo' v = KeyCommandsView.alloc().initWithFrame_(((0, 0), (400, 400))) v.setBackgroundColor_(UIColor.lightGrayColor()) v.becomeFirstResponder() ObjCInstance(main_view).addSubview_(v) label = ui.Label(frame=(0, 0, 400, 400)) label.alignment = ui.ALIGN_CENTER label.text = 'Press Cmd+R on an external keyboard to change the background color.' label.number_of_lines = 0 main_view.add_subview(label) main_view.present('sheet') if __name__ == '__main__': main()
-
@omz Thanks for the sample. It works like magic! Now my homework is to implement this into the existing TextView!
-
@omz Is it possible to find out the key and modifier information inside the action function, i.e. inside
keyCommandAction(_self, _cmd)
how can I tell which key is pressed? This is useful when a single action function is used to handle multiple keyboard shortcuts.I tried to check
_cmd
, but callingObjCInstance(_cmd)
crashed the app. -
@ywangd Yes, that's possible. Below is a modified version of the example I posted that prints the actual key command to the console. Because this only makes sense with multiple commands, I've also added a simple Cmd+B shortcut to make the background color blue (Cmd+R still chooses a random color).
The most important change is that the action now takes a "sender" argument.
_cmd
is a hidden argument that is passed to every ObjC method, it doesn't have anything to do with the key command here – it's the selector that was used to send the message, and you only need this in very rare cases, if ever.# coding: utf-8 from objc_util import * from random import random import ui UIKeyCommand = ObjCClass('UIKeyCommand') modifiers = {(1<<17): 'Shift', (1<<18): 'Ctrl', (1<<19): 'Alt', (1<<20): 'Cmd', (1<<21): 'NumPad'} def keyCommands(_self, _cmd): cmd_key_flag = (1<<20) key_command_r = UIKeyCommand.keyCommandWithInput_modifierFlags_action_('R', cmd_key_flag, 'keyCommandAction:') key_command_b = UIKeyCommand.keyCommandWithInput_modifierFlags_action_('B', cmd_key_flag, 'keyCommandAction:') commands = ns([key_command_r, key_command_b]) return commands.ptr def canBecomeFirstResponder(_self, _cmd): return True def keyCommandAction_(_self, _cmd, _sender): self = ObjCInstance(_self) key_cmd = ObjCInstance(_sender) flags = key_cmd.modifierFlags() modifier_str = ' + '.join(modifiers[m] for m in modifiers.keys() if (m & flags)) key_input = key_cmd.input() print 'Input: "%s" Modifiers: %s' % (key_input, modifier_str) if str(key_input) == 'R': r, g, b = random(), random(), random() else: r, g, b = 0.0, 0.0, 1.0 self.setBackgroundColor_(UIColor.colorWithRed_green_blue_alpha_(r, g, b, 1)) KeyCommandsView = create_objc_class('KeyCommandsView', UIView, [keyCommands, canBecomeFirstResponder, keyCommandAction_]) @on_main_thread def main(): main_view = ui.View(frame=(0, 0, 400, 400)) main_view.name = 'Key Commands Demo' v = KeyCommandsView.alloc().initWithFrame_(((0, 0), (400, 400))) v.setBackgroundColor_(UIColor.lightGrayColor()) v.becomeFirstResponder() ObjCInstance(main_view).addSubview_(v) label = ui.Label(frame=(0, 0, 400, 400)) label.alignment = ui.ALIGN_CENTER label.text = 'Press Cmd+R on an external keyboard to change the background color. Cmd+B makes the background blue.' label.number_of_lines = 0 main_view.add_subview(label) main_view.present('sheet') if __name__ == '__main__': main()
-
Thanks @omz Works perfectly!
-
@omz: Simple learning question, please - why is it important in this case to run the main() on_main_thread?
-
@mikael Basically, everything that involves UIKit has to run on the main thread. From Apple's documentation:
For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.
In this example,
main()
is the only function that is actually decorated withon_main_thread
, but the callbacks (keyCommands
,canBecomeFirstResponder
) actually also run on the main thread because they're called by UIKit.When you use the
ui
module, this is handled automatically (internally, a lot of things are dispatched to the main thread, without you having to declare it), but asobjc_util
is more low-level, this has to be done explicitly. -
Btw, in case you want to create keyboard shortcuts involving the arrow keys, you can use these special strings for the
input
argument:'UIKeyInputLeftArrow'
'UIKeyInputRightArrow'
'UIKeyInputUpArrow'
'UIKeyInputDownArrow'
There's also
'UIKeyInputEscape'
, but I wouldn't use that because not all iPad keyboards actually have an esc key. -
@omz: Thanks. If I am reading this right, I need to go back and decorate some gesture-adding functions.