Checking keyDown event.modifierFlags yields error - macos

Im subclassing NSTextView and overriding keyDown. I want to detect command-key-combinations. Command-L, for example.
Apple's documentation indicates that you simply and the modifier flags (in the passed NSEvent) with NSEventModifierFlags.CommandKeyMask.
When I do so:
let ck = NSEventModifierFlags.CommandKeyMask
I receive an odd error:
Binary operator '&' cannot be applied to two 'NSEventModifierFlags' operands.
What's the deal? This is swift 2.0, xcode 7.
Thanks!

Apple's documentation indicates that you simply and the modifier flags
The documentation is still referring to C and Objective-C. Swift uses OptionSetType, which does not use bitwise operators for checking flags.
Instead, use the contains() method to check for one or more flags:
if theEvent.modifierFlags.contains(.CommandKeyMask) {
NSLog("command key down")
}
if theEvent.modifierFlags.contains(.AlternateKeyMask) {
NSLog("option key down")
}
if theEvent.modifierFlags.contains([.CommandKeyMask, .AlternateKeyMask]) {
NSLog("command and option keys down")
}
To check for a single key, use intersect to filter out any unwanted flags, then use == to check for a single flag:
let modifierkeys = theEvent.modifierFlags.intersect(.DeviceIndependentModifierFlagsMask)
if modifierkeys == .CommandKeyMask {
NSLog("Only command key down")
}

NSEventModifierFlags is an optionSet in Swift 2.0. You can use contain method to check it contains the command modifier key
override func keyDown(theEvent:NSEvent) {
if theEvent.characters == "l" && theEvent.modifierFlags.contains(.CommandKeyMask) {
print("command-L pressed")
}
}

I was going to put in a comment, but wasn't able to.
In case someone (like me) comes across this article in the future, Swift has changed a little since 2015.
Swift 4:
theEvent.modifierFlags.contains(.command)
theEvent.modifierFlags.contains(.option)
theEvent.modifierFlags.contains([.command, .option])
NSLog("command and option keys down")
also; (.control) is for CTRL.
This is my particular code:
override func viewDidLoad()
{
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.keyDown, handler: myKeyDownEvent)
}
func myKeyDownEvent(event: NSEvent) -> NSEvent {
if (event.keyCode == 121) && event.modifierFlags.contains([.command, .option]) {
//Do what you want when PGDN + cmd + alt is pressed
}
return event
}

Related

Quick use of MASShortcut framework on Swift project

I am having trouble implementing MASShortcut (docs here) in a Swift OSX project to listen for global hotkeys. I have managed to import the framework via CocoaPods, and I have a working MASShortcutView instance:
#IBOutlet weak var testShortcutView:MASShortcutView!
I also figured out how to monitor and trigger something with the shortcut (please tell me if this is correct):
let shortcut = MASShortcut(keyCode: keycode, modifierFlags: modifierkeys)
MASShortcutMonitor.sharedMonitor().registerShortcut(shortcut, withAction: callback)
The question here is, how can I get the keyCode and modifierFlags from my MASShortcutView?
I really thank you in advance, I searched everywhere and I can't find an example on how to do this on swift. All I can find is is objective-c, and I can't figure it out.
Following code will register shortcut handler for Cmd+Shift+K key combination
let shortcut = MASShortcut.init(keyCode: UInt(kVK_ANSI_K), modifierFlags: UInt(NSEventModifierFlags.CommandKeyMask.rawValue + NSEventModifierFlags.ShiftKeyMask.rawValue))
MASShortcutMonitor.sharedMonitor().registerShortcut(shortcut, withAction: {
print("Hello world")
})
Cmd and Shift - modifing keys. You should set them in the modifierFlags parameters. Full list of possible values is available in NSEventModifierFlags enum.
For your convenience I have placed sample project on github:
https://github.com/melifaro-/ShortCutSwiftSample
That handles shortcuts changes:
shortcutView.shortcutValueChange = { (sender) in
let callback: (() -> Void)!
if self.shortcutView.shortcutValue.keyCodeStringForKeyEquivalent == "k" {
callback = {
print("K shortcut handler")
}
} else {
callback = {
print("Default handler")
}
}
MASShortcutMonitor.sharedMonitor().registerShortcut(self.shortcutView.shortcutValue, withAction: callback)
}
I have pushed changes into the repo. I would recommend to try following scenario:
Using the shortcut view:
Set Cmd+Shift+K shortcut
Set Cmd+Shift+J shortcut
Try this shortcuts - different callbacks should be performed
Hope it helps.
In Swift 5
let shortcut = MASShortcut(keyCode: kVK_ANSI_K, modifierFlags: [.command, .shift])
MASShortcutMonitor.shared()?.register(shortcut, withAction: {
print("hello")
})

'#selector' refers to a method that is not exposed to Objective-C

The new Xcode 7.3 passing the parameter via addTarget usually works for me but in this case it's throwing the error in the title. Any ideas? It throws another when I try to change it to #objc
Thank you!
cell.commentButton.addTarget(self, action: #selector(FeedViewController.didTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
The selector it's calling
func didTapCommentButton(post: Post) {
}
In my case the function of the selector was private. Once I removed the private the error was gone. Same goes for fileprivate.
In Swift 4
You will need to add #objc to the function declaration. Until swift 4 this was implicitly inferred.
You need to use the #objc attribute on didTapCommentButton(_:) to use it with #selector.
You say you did that but you got another error. My guess is that the new error is that Post is not a type that is compatible with Objective-C. You can only expose a method to Objective-C if all of its argument types, and its return type, are compatible with Objective-C.
You could fix that by making Post a subclass of NSObject, but that's not going to matter, because the argument to didTapCommentButton(_:) will not be a Post anyway. The argument to an action function is the sender of the action, and that sender will be commentButton, which is presumably a UIButton. You should declare didTapCommentButton like this:
#objc func didTapCommentButton(sender: UIButton) {
// ...
}
You'll then face the problem of getting the Post corresponding to the tapped button. There are multiple ways to get it. Here's one.
I gather (since your code says cell.commentButton) that you're setting up a table view (or a collection view). And since your cell has a non-standard property named commentButton, I assume it's a custom UITableViewCell subclass. So let's assume your cell is a PostCell declared like this:
class PostCell: UITableViewCell {
#IBOutlet var commentButton: UIButton?
var post: Post?
// other stuff...
}
Then you can walk up the view hierarchy from the button to find the PostCell, and get the post from it:
#objc func didTapCommentButton(sender: UIButton) {
var ancestor = sender.superview
while ancestor != nil && !(ancestor! is PostCell) {
ancestor = view.superview
}
guard let cell = ancestor as? PostCell,
post = cell.post
else { return }
// Do something with post here
}
Try having the selector point to a wrapper function, which in turn calls your delegate function. That worked for me.
cell.commentButton.addTarget(self, action: #selector(wrapperForDidTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
-
func wrapperForDidTapCommentButton(post: Post) {
FeedViewController.didTapCommentButton(post)
}
As you know selector[About] says that Objective-C runtime[About] should be used. Declarations that are marked as private or fileprivate are not exposed to the Objective-C runtime by default. That is why you have two variants:
Mark your private or fileprivate method declaration by #objc[About]
Use internal, public, open method access modifier[About]

'queryForTable' cannot return 'nil' In Swift

I am using PFQueryTableViewController as part of ParseUI to load a table of objects based on the currentUser's geolocation. I have seen several other (older) forum posts (like here) detailing that the queryForTable function should return nil if a value like currentLocation:PFGeoPoint? is nil. Then wait on the background process in viewDidLoad to get the PFGeoPoint and loadObjects(), thus calling the queryForTable function again.
I am seeing others say that Swift's ParseUI libraries may not have a queryForTable function that allows nil returns.
var currentLocation:PFGeoPoint? = nil
override func viewDidLoad() {
if currentLocation == nil {
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint?, error: NSError?) -> Void in
if error == nil {
print("Got the user's current location!")
self.currentLocation = geoPoint
print("Reloading table using user's location")
self.loadObjects()
}
}
}
}
override func queryForTable() -> PFQuery {
if self.currentLocation != nil {
print("Generating query based on user's location!")
let query = PFQuery(className:self.parseClassName!)
query.whereKey("taskLocation", nearGeoPoint: self.currentLocation!, withinMiles: 50)
if(self.objects?.count == 0)
{
query.cachePolicy = PFCachePolicy.CacheThenNetwork
}
return query
}
print("User's current location is not known")
return nil
}
Obviously, this fails to build because the function is not (note the question mark):
override func queryForTable() -> PFQuery? {
...
}
My attempted workaround was to return PFQuery() instead of nil, but I believe it returns after the viewDidLoad self.loadObjects(). The behavior I see is a momentary flash of a table with cell results, and then an empty table.
Here is a link to the Google Group discussion about this very issue, with Hector Ramos saying that it works with Objective-C but not Swift (...yet?). I'm running the latest ParseUI as of this posting (1.1.6).
Is there an option yet to do this in Swift? If not, when?
If this isn't an option, what are some workarounds people have tried successfully?
I actually figured this issue out - nothing to do with the code itself. Apparently, when you set a fake location on the iOS Simulator, it also enforces that different location when you use a real iOS device plugged in. My results were not showing up because there legitimately wasn't an object with a PFGeoPoint near my location (because it thought I was in London!)
Anyway, moral of the story is to make sure you always know your preset location in both Simulator and physical iOS devices.
PS - The code above does work when my location is set correctly.

Issue between override and non-override function in swift

Sorry I'm not very good at explaining this stuff. Basically I have the function below to handle remote control events.
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
override func remoteControlReceivedWithEvent(event: UIEvent) {
if event.type == UIEventType.RemoteControl {
if event.subtype == UIEventSubtype.RemoteControlPlay {
stream.play()
} else if event.subtype == UIEventSubtype.RemoteControlPause {
stream.stop()
} else if event.subtype == UIEventSubtype.RemoteControlTogglePlayPause {
toggle()
}
}
}
Essentially, when I use the term "override" (shown above), I get the error
"Method does not override any method from its superclass.
If I leave out the "override", I get the error:
"Method 'remoteControlReceivedWithEvent' with Objective-C selector 'remoteControlReceivedWithEvent:' conflicts with method "remoteControlReceivedWithEvent" from superclass "UIResponder" with the same Objective-C selector.
I'm pretty new to all of this so I don't really understand what the issue is. Can someone please explain how to remedy this issue? Let me know if you need some more code or something.
Is there more code I have to use to set this up?
There is a mismatch of UIResponder method signature and your function implementation. UIResponder has optional Event as following:
func remoteControlReceibedWithEvent(_ event: UIEvent?)
So it can not override as there is no function with non-optional argument, but if you remove override it will conflict with ObjC implementation, as selector names are the same.

Multiline NSTokenField and the return key

I have a NSTokenField object with multilines (just resized the control vertically in interface builder). Unfortunately the NSTokenField object does not insert new line when pressing the return key. Any ideas how to make the control insterting a new line when pressing the return key?
Do I really have to go the complicated way through delegates like control:textView:doCommandBySelector: ?
Thank you.
By default, text fields (and by extension token fields) do not insert a new line when pressing the return key. This is the expected behavior.
A user who wishes to enter a return instead of tokenizing or committing the input may press Option-Return.
If you wish to disable this behavior programatically (be careful not to break the user's expectations) then the delegate is by far the easiest way to do so.
I am afraid there is no other simpler way.
But it is not too complicated anyway - here the swift code to use in the delegate, so that enter and tab presses actually insert new lines and tabs :
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool
{
if (commandSelector == #selector(insertNewline))
{
textView.insertText("\n", replacementRange: textView.selectedRange)
return true
}
else if (commandSelector == #selector(insertTab))
{
textView.insertText("\t", replacementRange: textView.selectedRange)
return true
}
return false
}

Resources