You may experience a problem where UIPress.key is nil when you are building a Catalyst app for macOS 11 Big Sur and trying to evaluate keyboard input. The code to reproduce the issue is following (in any UIViewController):
#if targetEnvironment(macCatalyst)
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
//
for press in presses {
guard let key = press.key else { continue }
NSLog("Key Pressed: %#", key.charactersIgnoringModifiers) // <-- will never be logged on Big Sur
}
}
#endif
Running this with Catalina will work, but fails on Big Sur. I have opened a bug report to Apple since I thought this is a bug with Big Sur, but the answer to solve the problem was quite easy:
Simply implement the override for pressesBegan, and it starts to work. So, adding this:
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
//
for press in presses {
guard let key = press.key else { continue }
NSLog("Key press began: %#", key.charactersIgnoringModifiers)
}
}
and the issue is solved.
That's it! I want to share it here, because you may suffer from same issue. But I have also a question to you: For me it's somewhat weird that I need to implement another override to make the desired override working. Do you know the reason here? Is this anywhere documented?
Thanks much.
Also important to note that if you callsuper.pressesEnded(presses, with: event) before accessing the value of UIPress.key then UIPress.key will be nil.
Related
I have hotkeys on my app when using a physical keyboard and all the keys work fine except for the return key. Is there a reason why or a workaround to make this work? Thanks.
extension testViewController {
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else {return}
if key.keyCode == .keyboardReturn {
print("KEY PRESSED \(key.debugDescription)")
}
}
}
There are two keycodes for return which are .keyboardReturn and .keyboardReturnOrEnter
I just had to use .keyboardReturnOrEnter which I didn't see at the time.
I am trying to do something when a given key is pressed in a macOS App. First, I ran into a problem where the keyDown event was detected multiple times on each press, therefore executing the handler multiple times. As per a suggestion, I added code to check whether the event is a repeat and it seemed to work at the time. However, this solution seems to work only some of the time, other times the event is getting detected multiple times. Also, I can't seem find a pattern in situations when it works and when it doesn't.What might be the problem and how could I fix it.
Code:
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: checkKeyDown(event:))
}
func checkKeyDown(event: NSEvent) -> NSEvent{
if event.isARepeat == false{
if event.keyCode == 36{
print("Hello World!")
}
}
return event
}
Removing the event monitor when the window closes seems to have fixed this issue.
var numKeyDown : Any?
override func viewDidLoad() {
super.viewDidLoad()
numKeyDown = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: checkKeyDown(event:))
}
override func viewWillDisappear(){
if let numMonitor = self.numKeyDown {
NSEvent.removeMonitor(numMonitor)
}
}
func checkKeyDown(event: NSEvent) -> NSEvent{
if event.isARepeat == false{
if event.keyCode == 36{
print("Hello World!")
}
}
return event
}
My goal is to call doSomething whenever the user presses "up" on their Apple TV remote, only while the topButton is already focused.
The goal is that doSomething should NOT get called if bottomButton was focused. However, when user presses up, the focus moves up to topButton before calling pressesBegan. This causes doSomething to get called regardless of which button was focused.
func doSomething() {
// goal is this function should only run when topButton is focused
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard presses.first?.type == UIPress.PressType.upArrow else { return }
print(self.topButton.isFocused)
print(self.bottomButton.isFocused)
if self.topButton.isFocused {
doSomething()
}
}
Note that this workaround doesn't work:
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
guard let previouslyFocusedItem = context.previouslyFocusedItem as? UIButton else { return }
if previouslyFocusedItem == topButton {
self.doSomething()
}
}
Because the focus doesn't update if user presses up while on topButton.
Which leads to more complicated code (would have to add properties and weird-looking if-statements between the two functions that confuse other developers). Is there a more efficient workaround?
Was able to solve it using UIFocusGuide instead. Another issue I found with pressesBegan was that it didn't detect if user swipes up. UIFocusGuide handles both cases.
Im am trying to disable error sound when I push down space bar and arrow keys. I tryed handling events with super.keyDown(with: event) no luck. Cant find any other working solutions apart from using global key frameworks. Are there any other options I have?
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in
self.keyDown(with: aEvent)
return aEvent
}
}
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
}
Update: I've found out that the root cause of the problem was, that a view was the first responder that shouldn't be actually. After setting the responder to nil self.view.window?.makeFirstResponder(nil) I was able to fix this. I've also used performKeyEquivalent as this answer suggested.
I know my answer is very late, but maybe it will help you or someone else in the future. I'm not sure if that's the best way to do it but it does work. Simply return nil instead of the event.
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in
self.keyDown(with: aEvent)
return nil
}
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
}
Apple's documentation of the method says the following for the block parameter:
The event handler block object. It is passed the event to monitor. You can return the event unmodified, create and return a new NSEvent object, or return nil to stop the dispatching of the event.
The only downside is, that there will be no sound at all. Even if the key event is not handled by you.
I want to detect keyboard input in my NSViewController.
The idea is to have several actions performed if the user presses certain keys followed by ENTER/RETURN.
I have checked if keyDown would be a appropriate way. But I would receive an event any time the user has pressed a key.
I also have though on using an NSTextField, set it to hidden and let it have the focus.
But maybe there are other better solution.
Any ideas?
Thanks
I've finally got a solution that I like.
First it has nothing todo with any hidden UI Elements but rather let the viewcontroller detect keyboard input.
var monitor: Any?
var text = ""
override func viewDidLoad() {
super.viewDidLoad()
self.monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: myKeyDownEvent)
}
override func viewWillDisappear() {
//Clean up in case your ViewController can be closed an reopened
if let monitor = self.monitor {
NSEvent.removeMonitor(monitor)
}
}
// Detect each keyboard event
func myKeyDownEvent(event: NSEvent) -> NSEvent {
// keyCode 36 is for detect RETURN/ENTER
if event.keyCode == 36 {
print(text)
text = ""
} else {
text.append( event.characters! )
}
return event
}