No keyboard capture without title bar - macos

I am trying to build an animation using spriteKit and that could be controlled via keyboard (arrow keys to speed up, slow down, rewind the animation).
Also I need this application to have a completely transparent background, this I managed using :
scene?.backgroundColor = NSColor.clearColor()
as well as :
self.window.opaque = false
Up until now everything is working fine and I am able to control my animation. But as soon as I try to remove the title bar in Interface Builder by unchecking the box for my window on the right panel the keyboard capture stops working.
keyDown: is not called anymore and I get that 'dong' sound characteristic of when your mac tells you that keyboard input is not an option. Even though I still have the name of my app on the menu bar.
Is there any way to still be able to receive keyboard input when the title is off ?

By default NSWindow instances return false from canBecomeKeyWindow if the window has no title bar. The following quote is from the relevant section in the NSWindow class reference.
Attempts to make the window the key window are abandoned if this method returns false. The NSWindow implementation returns true if the window has a title bar or a resize bar, or false otherwise.
So, to get the behaviour you're after you need to subclass NSWindow, and return true from canBecomeKeyWindow.

As pointed out by Paul Pattersion (accepted answer) the trick was to subclass NSWindow in order to return true for canBecomeKeyWindow. For anyone wondering how to do that, here is the code:
import Cocoa
class CustomWindow: NSWindow {
override var canBecomeKeyWindow: Bool {
get { return true }
}
}

Related

Close borderless floating NSWindow on click outside

I want to create a borderless window floating above the main window. The main window should be the key window (I want it to handle the keyboard and mouse events). Also, the floating window should close as the user clicks outside of it. Basically, I'm creating a very custom context menu, just like NSMenu. It should behave likewise.
So I created my window this way:
NSWindow *menuWindow = [NSWindow windowWithContentViewController:menuViewController];
menuWindow.styleMask = NSBorderlessWindowMask;
menuWindow.level = NSFloatingWindowLevel;
[menuWindow makeKeyAndOrderFront:self];
That works perfectly, but how do I handle clicks outside to close it? The window doesn't invoke delegate's windowDidResignKey, because it's not a key window. If it does overload canBecomeKeyWindow (so that it returns YES), then the floating window grabs the user input, which is not what I want.
So, is there any way to automatically close a borderless window? Can NSPanel help? I tried using it, but with no success (the becomesKeyOnlyIfNeeded selector doesn't do what I need).
To detect clicks outside the window but within your app, you could install an event monitor using +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. If you also want to close the window when the user clicks in a different app, you could observe the NSApplicationDidResignActiveNotification notification.
NSWindow has a property hidesOnDeactivate that should do this for you.
From the Apple Docs:
The value of this property is true if the window is removed from the
screen when its application is deactivated; false if it remains
onscreen. The default value for NSWindow is false; the default value
for NSPanel is true.

Interface Builder: determine which element has the initial focus

I have a Cocoa app with a table view and a few other controls. When the app launches and the window is shown, a blue focus ring is drawn around the table view.
How can I get rid of that focus ring? I'd like nothing to have the focus when the window first shows.
The window has initialFirstResponder binding that shows which control will be active when the window becomes active. Change the initialFirstResponder or adjust tableview settings in interface builder to hide the focus ring
The best way I've found of stopping any of the controls from being the first responder when a window is first displayed is in the window controller:
Swift 3:
class YourWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Wait a frame before setting the first responder to be the window itself.
// We can't just set it right now, because if the first responder is set
// to the window now the system just interprets that as meaning that we
// want the default behavior where it automatically selects a view to be
// the first responder.
DispatchQueue.main.async {
window!.makeFirstResponder(nil)
}
}
}
It's messy, and sometimes when the window loads you see the focus ring starting to appear on one of the controls for one frame, but I haven't found a better way yet.

Not being able to edit NSTextField on NSPopover even though Editable behavior is set

I have an application, which open popover with NSTextField. The text field is not editable. Behavior for text field is set to Editable. I still can paste and copy text to this field but i can't edit it.
Anyone knows, what can be wrong?
Not sure if you still need the answer, but there may be some others still looking. I found a solution on apple developer forums. Quoting the original author:
The main problem is the way keyboard events works. Although the NSTextField (and all its superviews) receives keyboard events, it doesn't make any action. That happens because the view where the popover is atached, is in a window which can't become a key window. You can't access that window in any way, at least I couldn't. So the solution is override the method canBecomeKeyWindow for every NSWindow in our application using a category.
NSWindow+canBecomeKeyWindow.h
#interface NSWindow (canBecomeKeyWindow)
#end
NSWindow+canBecomeKeyWindow.m
#implementation NSWindow (canBecomeKeyWindow)
//This is to fix a bug with 10.7 where an NSPopover with a text field cannot be edited if its parent window won't become key
//The pragma statements disable the corresponding warning for overriding an already-implemented method
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (BOOL)canBecomeKeyWindow
{
return YES;
}
#pragma clang diagnostic pop
#end
That makes the popover fully resposive. If you need another window which must respond NO to canBecomeKeyWindow, you can always make a subclass.
I struggled with this for a while as well, until I realized it was a bug.
However, instead of relying on an isActive state of a NSStatusItem view, I find it much more reliable to use the isShown property of the NSPopover you have implemented.
In my code, I have a NSPopover in a NSViewController:
- (BOOL)canBecomeKeyWindow
{
if([self class]==NSClassFromString(#"NSStatusBarWindow"))
{
NSPopover *mainPopover = [[((AppDelegate*)[NSApp delegate]) mainViewController] mainPopover];
if(![mainPopover isShown])
return NO;
}
return YES;
}
Balazs Toth's answer works, but if you're attaching the popover to NSStatusItem.view the status item becomes unresponsive - requiring two clicks to focus.
What i found when working with this solution is that when NSStatusItem becomes unresponsive, you can easily override this behavior like this
- (BOOL)canBecomeKeyWindow {
if([self class]==NSClassFromString(#"NSStatusBarWindow")) {
CBStatusBarView* view = [((CBAppDelegate*)[NSApp delegate]) statusItemView];
if(![view isActive]) return NO;
}
return YES;
}
You will check for the class of the window, if it matches the NSStatusBarWindow we can then check somehow if the NSStatusItem is active. If it is, that means we have to return YES, because this way the NSPopover from NSStatusItem will have all keyboard events.
What I'm using for checking if the NSStatusItem was clicked (or is active) is that in my own custom view i have a bool value which changes when user clicks on the NSStatusItem, system automatically checks for "canBecomeKeyWindow" and when it does it will return NO and after user clicks on it (while it is returning the NO) it will change the bool value and return YES when system asks again (when NSPopover is being clicked for NSTextField editing).
Sidenotes:
CBStatusBarView is my custom view for NSStatusItem
CBAppDelegate is my App Delegate class
If anyone is still looking for an answer to this, I am working in Swift.
At the time where you wish the field to allow text entry, I have used myTextField.becomeFirstReponder()
To opt out; just use myTextField.resignFirstResponder()
Definitely a bug. That bug report is exactly what I was trying to do. Even down to creating the status item and overriding mousdown.
I can confirm that Balazs Toth's answer works. I just wonder if it might get in the way down the road.
If someone gets it and the solution above didn't do the trick for him.
The problem in my app was in the info tab in the targets my application was set to
Application is background only = true
and shulde of been
Application is agent = true
Spent an entire day on this thing.
Bug. http://openradar.appspot.com/9722231

Stopping a popover window becoming the key window

I am trying to write a Mac application which runs in the menubar, which when clicked, displays an iOS-like popover. Similar to Flexibit's Fantastical, or The Iconfactory's Take Five.
I am using INPopoverController which seems to work great.
My problem is that when the user clicks on anything in the popover view, the popover becomes the key window. Is there a way to stop this from happening?
In INPopoverWindow:
- (BOOL)canBecomeKeyWindow
{
return YES;
}
means that the popover can become a key window. You can either change it to return NO, or subclass INPopoverWindow and override that method to return NO.

Cocoa/Objective-C - Child window with text input without main window becoming inactive

I have a need to spawn a window that will hover just above my main window in a cocoa application. I want this main window to allow the user to enter some text in an input box. All is well until the text input box actually gains focus. The main window becomes "deactivated." This window is borderless and is a slightly custom shape -- its more like a hover card than anything else, I suppose.
Basically, I'd like this thing to work almost exactly like Spotlight (Apple + Space) -- you can enter text, but this is such an an ancillary operation that in the context of the greater UX, you don't want the jarring effect of the main window graying out (becoming inactive). You'll notice when you have some application open and in-focus, spotlight will not cause the window of that application to become inactive.
This problem arises because text input seems to REQUIRE that the child window become the key window (it will not let you place the cursor in the text input field). When it becomes key, the main window becomes inactive.
So far I've tried:
Subclassing NSWindow for my main application and overriding isKeyWindow such that it only loses key when the application is no longer the users focus (as opposed to the window). This had the unintended effect of colliding with key status of the child window and having very strange effects on the keyboard input (some keys are not captured, like delete)
Creating a view instead of a window. Doesn't work because of this problem -- you cannot draw over a Webkit WebView these days.
Anybody Cocoa/OSX wizards have any ideas? I've become a little obsessed with this one. An itch I can't scratch.
Edit: have also tried overriding the following on the child window. When clicked, the window makes the main application window become inactive.
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)canBecomeMainWindow {
return NO;
}
Edit 2:
After screwing about with NSMenu for a while, I scrapped that approach. It seems I found something, however. In NSPanel there is a window style mask called:
NSNonactivatingPanelMask
The panel can receive keyboard input without activating the owning application.
Valid only for an instance of NSPanel or its subclasses; not valid for a window.
Trying this out now...
Edit 3: NSNonactivatingPanelMask did not do the trick. No ideas.
What you want is a window that can become the key window but which cannot become the main window. You could implement such a class yourself, but this is basically what NSPanel is for, so you might try that first.
I think this can help you:
[self.childWindow makeKeyAndOrderFront:self];

Resources