Using Arrow Key as Key Equivalents with NSMenuItem - objective-c

I'm experience some event handling issues when attempting to use arrow keys without modifiers as key equivalents for menu items in the main menu bar. The problem I'm experiencing is that the main menu bar is handling the key down event as a key equivalent event before a tableView is able to. When the tableView is the first responder, the up/down arrow keys do not change the tableView's selection but rather trigger the key equivalent in the main menu bar.
The reason for this is that the incoming keyDown event for an arrow key is first passed to performKeyEquivalent on the target window, which in turns passes that event down the chain. NSTableView does not respond to this so the event bubbles back up to the application where it next dispatches it to the main menu, via performKeyEquivalent, and thus the event is consumed.
If the main menu does not have a key equivalent, then the event goes back to the window and down the chain via keyDown, which the tableView does respond to and correctly handles.
This is documented by Apple (more or less) in their Event Handling Guide.
Is there a proper way to handle key equivalents like arrow keys without modifiers such that they both appear in the menu item when it's being displayed, but are also properly consumed by any subviews that might handle them?
I've tried various tricks, but each one has numerous pros-and-cons:
NSMenu delegate
One can implement menuHasKeyEquivalent but it appears that you have to implement that for the entire main menu. While you could easily filter out the arrow keys, you also have to validate every other key equivalent, which isn't very practical.
Subclass NSApplication
You can override sendEvent: in NSApplication but the logic for keeping track of where you are in the event handling chain gets a bit hairy.
NSEvent tap
Similar to subclassing NSApplication. Things are a bit cleaner here because I can cheat and have the event tap a bit closer to the tableView, but you're still left with a lot of logic to determine when the tap should consume the event and "force-feed" it to the tableView versus when you should let the event be handled normally.
I'm curious if anyone has any suggestions on how best to implement an arrow key as a key equivalent when no modifiers are present and a tableView might be present.
(macOS 10.11+)

Related

Handling tabs in Cocoa view containing a text field and scrolling view

In my Cocoa app, I have a window that contains an NSTextField (as is) and an NSScrollView (sub-classed). I've got an NSViewController that manages the window's NSView containing the text field and scrolling view.
At app startup, the NSTextField has focus, and typing enters characters into that text box. When I hit the TAB key, it loses focus. But nothing else in the interface, like the NSScrollView, gains focus. I can't tell where any key down events are going.
How does one transfer focus to the NSScrollView, so that key down events can affect it (e.g., arrow keys, implicit searching, etc.)?
First, you should try hitting Tab repeatedly to see if focus ever makes it to the scroll view or comes back around to the text field.
You don't say what's in the scroll view as the document view. Rather than having the scroll view itself accept focus, it's more common that the document view or one of its descendant views accepts focus. Then, the movement keys would be delivered to that and, if nothing consumes them, they'd bubble up the responder chain to the scroll view and it would handle them automatically.
If you really want your scroll view to accept focus, you need to override the -acceptsFirstResponder method to return YES.
The behavior of Tab is governed by the window. It maintains a key view loop. It can automatically recalculate the key view loop as views are added and removed. That's probably the most reliable way. See the autorecalculatesKeyViewLoop property.
Alternatively, you can manually tell it to recalculate as you desire, by turning off autorecalculatesKeyViewLoop and calling -recalculateKeyViewLoop.
Or, you can explicitly set up the key view loop yourself by connecting each view's nextKeyView and previousKeyView properties, either in code or in Interface Builder.

Is there a way to disable sounds (such as "beeps") in my mac app?

I know I need to dig the reason why my app is beeping in the code, etc.
But I was wondering, is there a global setting to disable sounds all over my app screens?
this is very little information to go on, but usually your application is beeping when the responder chain comes up with no object that can respond to an event on the screen or keyboard.
For instance, if you type text in an active view and the view doesn't allow for text editing, the view sends the key down event to its super view. For a view this can end by the NSPanel or NSWindow or BSWindow controller. The last responder in the chain invokes the noResponderFor: method, which, when not implemented, will give a beep. If you don't want it to beep, override this method to do something else.
Based on your information I can't give you any other information.

How can I get the value of an NSSlider continuously?

It seems like NSSlider in Cocoa does not provide a delegate to receive an event like Value Changed for a UISlider.
How can I get the value of an NSSlider continuously and display it in an NSTextField, for example?
You need to research Cocoa's Target/Action mechanism. This is a basic Cocoa concept you'll need to understand. The slider (and any other control) can be given a target (some controller object) and an action (the method to call against that controller object).
The action is fired when the user stops dragging by default. Check the slider's Continuous property in Interface Builder to cause it to trigger the action as you're sliding it.
One advantage of using the timer approach is that it works for the case of using the keyboard rather than the mouse to adjust the slider. If the user has "Full Keyboard Access" turned on in System Preferences, they can use the Tab key to give the slider focus. They can then hold down an arrow key so that autorepeat kicks in, whereupon you have a similar situation to dragging with the mouse: the target/action is firing repeatedly, and you want to wait for a moment of calm before saving to the database.
You do need to be careful not to delete your NSTimer prematurely. For example, if the user quits the app during those couple of seconds you probably want to "flush" the slider value to the database before terminating the process.
Programmatical solution based on the answer of Joshua Nozzi:
Swift
slider.isContinuous = true
Objective-C
slider.continuous = YES;

Objective-C NSWindow remove window from key

Hi need a little help, i have a window which is set to always show in the top right corner but it is not set to always key. The window has a few buttons on it and when a button is clicked the window becomes key, but what i want it to do is when a button is clicked i want the window to remove itself from being key.
So ideally the window becomes key when a button is clicked and in the method which the button calls i want to write a statement which will then perform the action of the button and remove the window from key.
However the window is declared under the app delegate and the method linked to the button is declared in a separate header file.
Anyone have any ideas how i can do this, any help would be much appreciated.
Thanks in advance, Sami.
There are a few solutions depending on the architecture of your application.
Send [[NSApp mainWindow] makeKeyWindow], which will make the main window become key.
Your application delegate could have a reference to the main window. In the action method that handles the button click, you could ask the application delegate to make the main window become key. The application delegate would send [mainWindow makeKeyWindow].
Your application delegate could have a reference to the window controller that manages the main window. In the action method that handles the button click, you could ask the application delegate to make the main window become key. The application delegate would ask the main window controller to do that, and the main window controller would send [[self window] makeKeyWindow].
Your application delegate could listen to the NSWindowDidResignKeyNotification notification and keep a reference to the last window that resigned being key. In the action method that handles the button click, you could ask the application delegate to return key status to that previous window. The application delegate would need to ignore NSWindowDidResignKeyNotification notifications when the window is your auxiliary window. This solution is better when there’s no single main window.
If the first solution is not applicable, a) your application delegate could conform to a protocol that declares a method responsible for restoring key status to the proper window, or b) your action method could post a notification informing the application that your action method has completed, and have the application delegate listen to that notification and restore key status to the proper window.
Note that even though I’ve suggested that the application delegate would implement the behaviour of restoring key status, other objects could be responsible for that. This is particularly easier when notifications are used since there’s no need to grab a reference to the object that will restore key status due to the inherent loose coupling provided by notifications.

Correctly handling mouse events with NSControl and NSCell?

I'm working on a custom button component. I've been reading through the documentation for NSControl and NSCell, and the programming topics associated with it.
What I can't figure out is how to correctly use mouse events in NSCell's. I see that NSControl subclasses NSView, which implements NSResponder - so implementing mouse events in the control and forwarding to the cell is simple. But the documentation states that when a control needs to be placed in a Table, the Cell for the control is used, not the control.
The methods that are available in the cell are somewhat well documented, but confusing to understand how to implement. Ultimately what I'm trying to figure out is how to replicate a push button; a down image is shown while the mouse is down, until the mouse is up. I've already got the images and text etc working fine.
I've been experimenting and messing around with different combinations of the mouse methods for NSCell, but just can't get it right.
-Is using NSCell for mouse events in this respect even correct?
-Is using the mouse events from NSControl and forwarding to the cell the right way?
-If I use mouse events from the Control and forward to the cell, how does a Table know that I need certain mouse events forwarded to my cell?
-If the mouse events are needed from an NSControl, can a table use an NSControl?
Any takers?