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
}
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.
The requirement is simple: I want to start an activity (e.g. drawing circles on the screen) when the user touches the screen and holds down the finger. This activity shall stop immediately when the finger is lifted again. So I used the UILongPressGestureRecognizer. This produces a .began and .ended event. But the problem is that when an activity is launched based on the .began event, the .ended event will not happen as long as the activity is ongoing. My intention for the .ended event is that it shall stop the launched activity. In this way the activity execution would be limited to the time between finger touch down and lift.
I enhanced my sample code to even 2 independent UILongPressGestureRecognizers which can work simultaneously using the UIGestureRecognizerDelegate construct. The simultaneous function of the two recognizers works. However, when one of them launches the activity also the second recognizer does not receive events until the activity has ended.
How can this trivial request be realized?
Here is my sample code. The activity is simplified to 10 print statements. 1 per second. When the touch finger is lifted after 2 seconds, the loop will continue for the remaining 8 seconds.
import UIKit
class ViewController: UIViewController {
var fingerDown = false
#IBOutlet var MyLongPress1: UILongPressGestureRecognizer!
#IBOutlet var MyLongPress2: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func handleLongPress1(_ gesture: UILongPressGestureRecognizer) {
if (gesture as AnyObject).state == .began {
print("longPress1 began")
fingerDown = true
myActivity()
} else if (gesture as AnyObject).state == .ended {
print("longPress1 ended")
fingerDown = false
}
}
#IBAction func handleLongPress2(_ gesture: UILongPressGestureRecognizer) {
if (gesture as AnyObject).state == .began {
print("longPress2 began")
fingerDown = true
} else if (gesture as AnyObject).state == .ended {
print("longPress2 ended")
fingerDown = false
}
}
func myActivity() {
var count = 0
while fingerDown && count < 10 {
sleep(1)
count += 1
print("in myActivity \(count)")
}
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return true
}
}
Any good advice would be appreciated!
You are executing synchronous code in your function.
Instead, what you should do is create an asynchronous, cancellable operation, that starts on .began event, and cancel it on .ended event. Code for this would be too complicated to write and put here in an answer. You can instead refer a concurrently / asynchronous code tutorial like this one to properly implement what you are trying to do.
A much simpler, but dirtier solution that will still work is to simply call your function asynchronously, so it doesn't block the main thread:
DispatchQueue.global().async {
self.myActivity()
}
Having an async call like this in your code is ok, but there are other problems with the code which make it not very clean.
e.g. your code depends on an additional fingerDown state instead of actual state of gesture; use of sleep etc..
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
}
Using Xcode 8, Swift 3 and Interface Builder with Storyboards and a very basic view controller featuring only a button (Play) and a slider (volume).
I'm trying to use my NSSlider while pressing a button at the same time. For instance, I need to be able to keep pressing "Play" while slowly lowering the volume. What happens instead is that only one button can be in use at a time. So, as long as I am pressing "Play" using the return key, I can't adjust the volume and vice versa.
In this example, the Play button is triggered by the "return" key.
Here is the code in ViewController.swift. This is just an example app I created quickly to study the problem.
Is there an Interface Builder setting on the NSSLider that I am missing, or can I do something in my code to achieve what I am trying to do?
import Cocoa
import AVFoundation
class ViewController: NSViewController {
var audioPlayer = AVAudioPlayer()
var sliderValue: Float = 0
#IBOutlet weak var volumeSliderOutlet: NSSlider!
override func viewDidLoad() {
super.viewDidLoad()
do{
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "bell ding", ofType: "wav")!))
}
catch {
print(error)
}
audioPlayer.prepareToPlay()
// Do any additional setup after loading the view.
}
#IBAction func playButtonAction(_ sender: NSButton) {
if audioPlayer.isPlaying {
audioPlayer.currentTime = 0.0
audioPlayer.play()
} else {
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
#IBAction func volumeSliderAction(_ sender: NSSlider) {
if audioPlayer.isPlaying {
sliderValue = self.volumeSliderOutlet.floatValue
audioPlayer.volume = sliderValue
} else {
return
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I've also tried this, based on advice from #inspector_60, but it doesn't work either. Still can't handle the slider while continuing to press the shortcut key for the button "return".
override func keyDown(with event: NSEvent) {
if (event.keyCode == 36) {
self.playButtonAction(nil)
}
else {
return
}
}
func playButtonAction(_ sender: NSButton?) {
if audioPlayer.isPlaying {
audioPlayer.currentTime = 0.0
audioPlayer.play()
} else {
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
IBAction are UI events so not suitable for multiple simultaneous clicks (you have only one mouse pointer). Use handling key events in order to handle keyboard events for simultaneous actions.
You need to also implement the mouse events, these links should help:
Apple document about mouse events and specifically Filtering Out Key Events During Mouse-Tracking Operations
This stack overflow answer on how to implement this approach
This post that will give you more information and example - Ron's answer links to a question not that different from yours
I have a mouse down function that calls a function of another class in which the instance is named formation
override public func mouseDown(with event: NSEvent) {
let location = event.location(in: self)
let touchedNode = self.atPoint(location)
print(location)
else if touchedNode.name == "navRight"
{
formation.pageOne()
}
}
and with that line i call the pageOne() function
func pageOne()
{
print("in page one")
earthNode.runAction(cpalAccessor.arcRoll())
spaceNode.runAction(cpalAccessor.arcRoll())
}
and the output is simply in page one and the animations do not carry out. Can someone help me with this?