Detect space bar event on Mac? - macos

I've searched the entire internet for something on this with no luck. I'm trying to detect if the user hits the space bar on a Mac app.
-(void)keyDown:(NSEvent*)theEvent; works great when the user presses on a character key, but not enter or space.
-(BOOL)performKeyEquivalent:(NSEvent *)theEvent; is called when the user hits the enter key or the arrow keys, but not the space bar.
Any ideas?

Was able to get it working by subclassing NSWindow and implementing this method:
- (void)sendEvent:(NSEvent *)theEvent
{
NSString* keysPressed = [theEvent characters];
if ( [keysPressed isEqualToString:#" "] )
{
if(theEvent.type==NSKeyDown)
NSLog(#"spaceDown");
if(theEvent.type==NSKeyUp)
NSLog(#"spaceUp");
}
}

You can use this other loop with the key number 32 according to the space bar constant.
if ([theArrow length] == 1)
{
keyChar = [theArrow characterAtIndex:0];
NSLog(#"Dentro2 %hu", keyChar);
switch (keyChar)
{
case 32: // Space Bar management
break;
}
}
// Manage when any key is dropped.
if(downOrUp == FALSE)
{
NSLog(#"Tecla soltada.");
self.playerVelocity = CGPointMake(0.0, 0.0);
}
// Methods to handle key push and key drop
- (void)keyDown:(NSEvent *)event
{
[self handleKeyEvent:event keyDown:YES];
}
- (void)keyUp:(NSEvent *)event
{
[self handleKeyEvent:event keyDown:NO];
}

I stumbled upon it by myself resently. Strangely enough, keyDown: event is detected only by the the method described by moby. But keyUp: works like a charm with space bar press

After trying few solutions i come with one that din't break other things for me
#import Carbon;
typedef void (^CustomWindowSpacebarKeyCallback)(NSEventType eventType);
#interface CustomWindow : NSWindow
#property (nonatomic, copy) CustomWindowSpacebarKeyCallback spacebarKeyCallback;
#end
#implementation CustomWindow
- (void) sendEvent:(NSEvent *)theEvent
{
[super sendEvent:theEvent];
if (([theEvent type] == NSKeyDown || [theEvent type] == NSKeyUp) && _spacebarKeyCallback) {
NSString *keysPressed = [theEvent characters];
if ([keysPressed length] == 1) {
unichar keyChar = [keysPressed characterAtIndex:0];
if (keyChar == kSpaceCharCode) {
_spacebarKeyCallback([theEvent type]);
}
}
}
}
#end

Related

How to intercept toggling fullscreen mode

I'm creating Mac OS plugin(bundle) for Unity3D. How can I intercept entering cmd-f combination (toggling full screen mode)? I can't create my own window, I can only use default (mainWindow). I've tried to use NSNotificationCenter, but I need to stop event, I don't need just a notification. I've tried to create NSResponder and add it to capture input events, but something don't work. Any ideas how to do it?
NSWindow* window = [[NSApplication sharedApplication] mainWindow];
NSView* view = [window contentView];
NSResponder* oldresp = [view nextResponder];
MyResponder* myres = [MyResponder alloc];
[myres retain];
[view setNextResponder:myres];
and
#interface MyResponder : NSResponder
{
}
- (void)keyDown:(NSEvent *)theEvent;
#end
#implementation MyResponder
- (void)keyDown:(NSEvent *)theEvent
{
NSLog(#"%#",#"!KeyDown Event");
NSString *theArrow = [theEvent charactersIgnoringModifiers];
unichar keyChar = 0;
if ( [theArrow length] == 1 )
{
keyChar = [theArrow characterAtIndex:0];
if ( keyChar == NSModeSwitchFunctionKey )
{
NSLog(#"%#",#"!!!___!!! GOT NSModeSwitchFunctionKey !!!");
return;
}
NSLog(#"%# %d",#"! Key:",keyChar);
}
[super keyDown:theEvent];
}
#end
One solution is to check constantly if the user has switched to fullscreen mode, and if he has, toggle fullscreen off from your program. This may cause some brief lag or graphics sketchiness momentarily, but it should work.
function Update ()
{
if (Screen.fullScreen) {
Screen.fullScreen = false;
}
}
Im not sure of a way to intercept the key press and ignore the command before the program switches though.
See this related post on unity answers about dealing with this on windows:
http://answers.unity3d.com/questions/544183/block-or-override-alt-enter-fullscreen.html

Scrolling NSTextView to bottom

I'm making a little server app for OS X and I'm using an NSTextView to log some info about connected clients.
Whenever I need to log something I'm appending the new message to the text of the NSTextView this way:
- (void)logMessage:(NSString *)message
{
if (message) {
self.textView.string = [self.textView.string stringByAppendingFormat:#"%#\n",message];
}
}
After this I'd like the NSTextField (or maybe I should say the NSClipView that contains it) to scroll down to show the last line of its text (obviously it should scroll only if the last line is not visible yet, in fact if then new line is the first line I log it is already on the screen so there is no need to scroll down).
How can I do that programmatically?
Found solution:
- (void)logMessage:(NSString *)message
{
if (message) {
[self appendMessage:message];
}
}
- (void)appendMessage:(NSString *)message
{
NSString *messageWithNewLine = [message stringByAppendingString:#"\n"];
// Smart Scrolling
BOOL scroll = (NSMaxY(self.textView.visibleRect) == NSMaxY(self.textView.bounds));
// Append string to textview
[self.textView.textStorage appendAttributedString:[[NSAttributedString alloc]initWithString:messageWithNewLine]];
if (scroll) // Scroll to end of the textview contents
[self.textView scrollRangeToVisible: NSMakeRange(self.textView.string.length, 0)];
}
As of OS 10.6 it's as simple as nsTextView.scrollToEndOfDocument(self).
Swift 4 + 5
let smartScroll = self.textView.visibleRect.maxY == self.textView.bounds.maxY
self.textView.textStorage?.append("new text")
if smartScroll{
self.textView.scrollToEndOfDocument(self)
}
I've been messing with this for a while, because I couldn't get it to work reliably. I've finally gotten my code working, so I'd like to post it as a reply.
My solution allows you to scroll manually, while output is being added to the view. As soon as you scroll to the absolute bottom of the NSTextView, the automatic scrolling will resume (if enabled, that is).
First a category to #import this only when needed...
FSScrollToBottomExtensions.h:
#interface NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom;
- (BOOL)isAtBottom;
- (void)scrollToBottom;
#end
FSScrollToBottomExtensions.m:
#implementation NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom
{
NSRect visRect;
NSRect boundsRect;
visRect = [self visibleRect];
boundsRect = [self bounds];
return(NSMaxY(visRect) - NSMaxY(boundsRect));
}
// Apple's suggestion did not work for me.
- (BOOL)isAtBottom
{
return([self distanceToBottom] == 0.0);
}
// The scrollToBottom method provided by Apple seems unreliable, so I wrote this one
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
#end
... create yourself an "OutputView", which is a subclass of NSTextView:
FSOutputView.h:
#interface FSOutputView : NSTextView
{
BOOL scrollToBottomPending;
}
FSOutputView.m:
#implementation FSOutputView
- (id)setup
{
...
return(self);
}
- (id)initWithCoder:(NSCoder *)aCoder
{
return([[super initWithCoder:aCoder] setup]);
}
- (id)initWithFrame:(NSRect)aFrame textContainer:(NSTextContainer *)aTextContainer
{
return([[super initWithFrame:aFrame textContainer:aTextContainer] setup]);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
NSNotificationCenter *notificationCenter;
NSView *view;
// viewBoundsDidChange catches scrolling that happens when the caret
// moves, and scrolling caused by pressing the scrollbar arrows.
view = [self superview];
[notificationCenter addObserver:self
selector:#selector(viewBoundsDidChangeNotification:)
name:NSViewBoundsDidChangeNotification object:view];
[view setPostsBoundsChangedNotifications:YES];
// viewFrameDidChange catches scrolling that happens because text
// is inserted or deleted.
// it also catches situations, where window resizing causes changes.
[notificationCenter addObserver:self
selector:#selector(viewFrameDidChangeNotification:)
name:NSViewFrameDidChangeNotification object:self];
[self setPostsFrameChangedNotifications:YES];
}
- (void)handleScrollToBottom
{
if(scrollToBottomPending)
{
scrollToBottomPending = NO;
[self scrollToBottom];
}
}
- (void)viewBoundsDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)viewFrameDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)outputAttributedString:(NSAttributedString *)aAttributedString
flags:(int)aFlags
{
NSRange range;
BOOL wasAtBottom;
if(aAttributedString)
{
wasAtBottom = [self isAtBottom];
range = [self selectedRange];
if(aFlags & FSAppendString)
{
range = NSMakeRange([[self textStorage] length], 0);
}
if([self shouldChangeTextInRange:range
replacementString:[aAttributedString string]])
{
[[self textStorage] beginEditing];
[[self textStorage] replaceCharactersInRange:range
withAttributedString:aAttributedString];
[[self textStorage] endEditing];
}
range.location += [aAttributedString length];
range.length = 0;
if(!(aFlags & FSAppendString))
{
[self setSelectedRange:range];
}
if(wasAtBottom || (aFlags & FSForceScroll))
{
scrollToBottomPending = YES;
}
}
}
#end
... You can add a few more convenience methods to this class (I've stripped it down), so that you can output a formatted string.
- (void)outputString:(NSString *)aFormatString arguments:(va_list)aArguments attributeKey:(NSString *)aKey flags:(int)aFlags
{
NSMutableAttributedString *str;
str = [... generate attributed string from parameters ...];
[self outputAttributedString:str flags:aFlags];
}
- (void)outputLineWithFormat:(NSString *)aFormatString, ...
{
va_list args;
va_start(args, aFormatString);
[self outputString:aFormatString arguments:args attributeKey:NULL flags:FSAddNewLine];
va_end(args);
}
I have some customised NSTextView and custom input method so my option was to use:
self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))

Add HotKey to NSTextField

I have some trouble with my NSTextField subclass. It's a textfield for input some message. And My application must send this message when my sub-textfield is a first responder and the user presses hotkey Cmd + Enter. I can't use Carbon method RegisterEventHotKey() because many applications use this hotkey for the same action but my application intercepts it. What can I do?
In the text field delegate, you can follow all “standard” commands are sent to the control. Then, once you meet a command noop:, test for current key combination. If it is Command-Enter, do your thing. Something like this should work:
#interface VCEAppDelegate : NSObject <NSApplicationDelegate, NSTextFieldDelegate>
#property (nonatomic, weak) IBOutlet NSTextField *textField;
#end
// .m
#implementation VCEAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.textField.delegate = self;
}
- (BOOL)isCommandEnterEvent:(NSEvent *)e {
NSUInteger flags = (e.modifierFlags & NSDeviceIndependentModifierFlagsMask);
BOOL isCommand = (flags & NSCommandKeyMask) == NSCommandKeyMask;
BOOL isEnter = (e.keyCode == 0x24); // VK_RETURN
return (isCommand && isEnter);
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView
doCommandBySelector:(SEL)commandSelector {
if ((commandSelector == #selector(noop:)) &&
[self isCommandEnterEvent:[NSApp currentEvent]]) {
[self handleCommandEnter];
return YES;
}
return NO;
}
- (void)handleCommandEnter {
NSLog(#"Do something on Command-Enter");
}
#end
I subclassed NSApplication and override method
- (void) sendEvent:(NSEvent*) event {
if ([event type] == NSKeyDown) {
if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask) {
if ([event keyCode] == 36) {
if ([self.delegate respondsToSelector:#selector(sendMessage:)]) {
[self.delegate performSelector:#selector(sendMessage:) withObject:nil];
}
}
}
}
[super sendEvent:event];
}
in info.plist I wrote that this sub-NSApplication class is a Principal class. It works!

Get keyDown event for an NSTextField

In xcode last version, I am trying to get the keyDown event of an NSTextField.
However, despite following multiple tutorials on the internet (delegates, controllers...), I still can't receive it.
Any easy hint for me ?
Thanks !
I got sick of all the non answers to do it some other way people so I put my nose down and figured out a way to make this work. This isn't using keydown event directly but it is using the keydown in the block. And the behavior is exactly what I wanted.
Subclass the text field
.h
#interface LQRestrictedInputTextField : NSTextField
.m
In the become first responder setup a local event
static id eventMonitor = nil;
- (BOOL)becomeFirstResponder {
BOOL okToChange = [super becomeFirstResponder];
if (okToChange) {
[self setKeyboardFocusRingNeedsDisplayInRect: [self bounds]];
if (!eventMonitor) {
eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
NSString *characters = [event characters];
unichar character = [characters characterAtIndex:0];
NSString *characterString=[NSString stringWithFormat:#"%c",character];
NSArray *validNonAlphaNumericArray = #[#" ",#"(",#")",#"[",#"]",#":",#";",#"\'",#"\"",#".",#"<",#">",#",",#"{",#"}",#"|",#"=",#"+",#"-",#"_",#"?",#"#",
#(NSDownArrowFunctionKey),#(NSUpArrowFunctionKey),#(NSLeftArrowFunctionKey),#(NSRightArrowFunctionKey)];
if([[NSCharacterSet alphanumericCharacterSet] characterIsMember:character] || character == NSCarriageReturnCharacter || character == NSTabCharacter || character == NSDeleteCharacter || [validNonAlphaNumericArray containsObject:characterString ] ) { //[NSCharacterSet alphanumericCharacterSet]
} else {
NSBeep();
event=nil;
}
return event;
} ];
}
}
NSLog(#"become first responder");
return okToChange;
}
remove the event once the textfield editing ends
Also if you're using ARC I noticed you might need to assign the textview string to the stringValue. I nslog'd the stringValue and the value was retained. Without the nslog I had to assign the notification object string to the stringValue to keep it from getting released.
-(void) textDidEndEditing:(NSNotification *)notification {
[NSEvent removeMonitor:eventMonitor];
eventMonitor = nil;
NSTextView *textView=[notification object];
self.stringValue=textView.string;
}
You can subclass NStextField and use keyUp that works for NSTextField.
in .h
#interface MyTextField : NSTextField <NSTextFieldDelegate>
in .m
-(void)keyUp:(NSEvent *)theEvent {
NSLog(#"Pressed key in NStextField!");
}
Add UITextFieldDelegate to your .h like this
#interface ViewController : UIViewController <UITextFieldDelegate> {
Then you can use this to detect every key press in a text field
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Return YES to allow the character that was pressed to be inserted into the field but you can add whatever code you need in here.

How can I handle ESC key in Cocoa app?

I made an app switching to full screen mode. I want to use ESC key to escaping fullscreen mode, but binding menu item to ESC key in IB is removed at runtime. How can I keep ESC key binding to a menu item?
Preferred way to handle escape key in Cocoa is this as like #Josh Caswell said.
#pragma mark - NSResponder
- (void)cancelOperation:(id)sender
{
[self exitFullScreen];
}
One way to capture keyboard events involves subclassing:
Subclass your full-screen class (e.g.) NSView.
Add the method - (void) keyDown:(NSEvent *)theEvent to the subclass implementation.
Open up InterfaceBuilder and select the full-screen class that you previously created.
Change its class to your new subclass.
The subclass looks something like:
MySubclass.h
#interface MySubclass : NSView {
}
#end
MySubclass.m
#import <Carbon/Carbon.h>
#implementation MySubclass
- (void)keyDown:(NSEvent *)theEvent
{
switch([theEvent keyCode]) {
case kVK_Escape:
NSLog(#"ESC");
// Call the full-screen mode method
break;
default:
[super keyDown:theEvent];
}
}
#end
This doesn't bind the ESC key to the menu item, but it does give you equivalent functionality (and a bit more flexability since you can intercept all keyboard events).
Many people try to implement esc key functionality. There is cancelOperation in the responder chain to handle escape events.
//WRONG
- (void)keyDown:(NSEvent *)event
{
//unichar character = 0;
//if ([event type] == NSEventTypeKeyDown) {
// if ([[event charactersIgnoringModifiers] length] == 1) {
// character = [[event characters] characterAtIndex:0];
// }
//}
switch (character) {
//THIS IS WRONG correct is to implement interpretKeyEvents+moveRight
//case NSRightArrowFunctionKey:
// [self moveSelectedIndexRight];
// break;
//THIS IS WRONG correct is to implement interpretKeyEvents+ moveLeft
//case NSLeftArrowFunctionKey:
// [self moveSelectedIndexLeft];
// break;
//THIS IS WRONG correct is to implement interpretKeyEvents+ moveLeft
//case NSCarriageReturnCharacter:
// [self dismissWithCurrentlySelectedToken];
// break;
default:
[self interpretKeyEvents:#[event]];
[super keyDown:event]
break;
}
}
//CORRECT
- (void)keyDown:(NSEvent *)event
{
[self interpretKeyEvents:#[event]];
[super keyDown:event];
}
`/* Catch the commands interpreted by interpretKeyEvents:. Normally, if we don't implement (or any other view in the hierarchy implements) the selector, the system beeps. Menu navigation generally doesn't beep, so stop doCommandBySelector: from calling up t`he hierarchy just to stop the beep.
*/
- (void)doCommandBySelector:(SEL)selector {
if ( selector == #selector(moveRight:)
|| selector == #selector(moveLeft:)
|| selector == #selector(cancelOperation:)
|| selector == #selector(insertNewline:) )
{
[super doCommandBySelector:selector];
}
// do nothing, let the menu handle it (see call to super in -keyDown:)
// But don't call super to prevent the system beep
}
- (void)cancelOperation:(id)sender
{
//do your escape stuff
}
- (void)insertNewline:(id)sender
{
//do your enter stuff
}
- (void)moveRight:(nullable id)sender
{
[self moveSelectedIndexRight];
}
- (void)moveLeft:(nullable id)sender
{
[self moveSelectedIndexLeft];
}
I needed to dodge WKWebView crashes when ESC is pressed(?) so I sub-class it, and added:
import Carbon.HIToolbox
override func keyDown(with event: NSEvent) {
if event.keyCode == UInt16(kVK_Escape) {
// We crash otherwise, so just close window
self.window?.performClose(event)
}
else
{
// still here?
super.keyDown(with: event)
}
}

Resources