How can I respond to a keyUp event? - swift

I am writing a simple menu bar application for MacOS using SwiftUI. I would like the application to respond to the escape key. Using what I have pieced together so far, I have something like this:
extension NSWindow {
open override func keyDown(with event: NSEvent) {
print("keyDown: \(event.keyCode)")
}
open override func keyUp(with event: NSEvent) {
print("keyUp: \(event.keyCode)")
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// etc
}
}
The AppDelegate creates and launches a NSPopover.
When I run the application, I do get the keyUp messages, but, not the keyDown for some reason.
The question is how can I respond to the key from within the AppDelegate? Ultimately, I want to close the popover using the escape key, but I would also like to explore other possibilities.

Related

Swift Mouse Left Click Listener

I am wondering how I can get this function to work, I just don't understand how to get correct type for NSEvent or where I even find it using developer documentation.
func mouseDown(with event: NSEvent)
I am trying to make an IF Statement that prints "User has clicked mouse" within a console window.
I am not looking for you to solve this problem for me, I am just hoping that you can show me a way that I can solve this for myself using developer documentation, please don't just link me a copy paste thread from another stack overflow unless it teaches me how people are finding the information required to solve these problems.
I just don't understand what goes inside of the parameter (with event: NSEvent) or how you would even find this out without just copying someone else's work.
All you need is to override your view controller func mouseDown(with event: NSEvent) method. Just make sure you dont change the method signature and include the override keyword and dont forget to call super.
import Cocoa
class ViewController: NSViewController {
// ...
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
print(#function)
}
override func rightMouseDown(with event: NSEvent) {
super.rightMouseDown(with: event)
print(#function)
}
}

Prevent NSToolbarItem from being removed

I want to prevent certain toolbar items from being removed by the user. They should still be movable, just not removable.
I tried creating a custom subclass of NSToolbar with a custom removeItem(at:) implementation, but it seems this method is not even called if the user drags an item out of the toolbar in the customization palette.
The delegate also doesn't seem to expose functionality for this.
How can I disable removal of certain NSToolbarItems?
I am not sure if you can prevent it from being removed but you can implement the optional toolbarDidRemoveItem method and insert the item that you don't want it to be removed back:
import Cocoa
class WindowController: NSWindowController, NSToolbarDelegate {
#IBOutlet weak var toolbar: Toolbar!
override func windowDidLoad() {
super.windowDidLoad()
toolbar.delegate = self
}
func toolbarDidRemoveItem(_ notification: Notification) {
if let itemIdentifier = (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier,
itemIdentifier.rawValue == "NSToolbarShowColorsItem" {
toolbar.insertItem(withItemIdentifier: itemIdentifier, at: 0)
}
}
}
Since it is not super critical if they are removed in case a private API call would stop working, I opted for the private API solution.
extension NSToolbarItem {
func setIsUserRemovable(_ flag: Bool) {
let selector = Selector(("_setIsUserRemovable:"))
if responds(to: selector) {
perform(selector, with: flag)
}
}
}
This works exactly as advertised.

listening for multitouch events in OS X using the touchesBegan and touchesMoved event handlers

I am trying to record touches and finger movements that users perform on a 2018 MacBook Trackpad.
When I try to test the API and print to console when the user touches the Trackpad, I see no output.
How do I listen for touch events and get data from them?
I referred to the handling Trackpad events documentation. The handling multitouch events section of the page says:
A view by default does not accept touch events. To handle touch events, your custom view must first call the NSView method setAcceptsTouchEvents: with an argument of YES.
However, the setAcceptsTouchEvents documentation says the method is deprecated.
When I try just printing a log on detecting a touch to test if the API is working, I do not see any console output. here is my code:
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func touchesBegan(with event: NSEvent) {
print("touched!");
}
}
how do I get the console statement to print something?
I was playing around with this and here is what I figured out.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the //view.
//view.acceptsTouchEvents = true; //deprecated but works.deprecated
view.allowedTouchTypes = [NSTouch.TouchTypeMask.direct, NSTouch.TouchTypeMask.indirect];
}
and then, you can override the functions when touches are in different phases as explained in the question.
Note that to do the "allowedTouchTypes" massage in a subclass it is like this:
import Cocoa
class UnView: NSView{
override var allowedTouchTypes: NSTouch.TouchTypeMask {
get { return [] } .. as you wish
set { }
}
}

AppDelegate#applicationDidFinishLaunching not called for Swift 4 MacOS app built from command line

So here is the full code:
main.swift
import Cocoa
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
Swift.print("AppDelegate.init")
super.init()
Swift.print("AppDelegate.init2")
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
Swift.print("AppDelegate.applicationDidFinishLaunching")
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
Swift.print("AppDelegate.applicationShouldTerminateAfterLastWindowClosed")
}
}
Then I compile it with:
swiftc Sources/*
./main
It logs:
AppDelegate.init
AppDelegate.init2
But then it doesn't log anything else, so I close it with CTRL+C. Not sure why it's not reaching the applicationDidFinishLaunching method ever. Wondering if one knows of a fix for this, it seems like there is just one method that needs to be called somewhere, but I'm not sure.
I think this may be causing other issues such as NSMenu not working.
Hooray, it was just because of the format of the methods, which were silently failing I guess.
func applicationWillFinishLaunching(_ notification: Notification) {
Swift.print("AppDelegate.applicationWillFinishLaunching")
}
func applicationDidFinishLaunching(_ notification: Notification) {
Swift.print("AppDelegate.applicationDidFinishLaunching")
}

ViewController + Storyboard setting up validation with controlTextDidChange

Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.