NSTableView navigate with arrow keys - cocoa

How can I navigate through my table view with the arrow keys. Much like setAction: or setDoubleAction, but instead of reacting to clicks, react with the arrow keys moving up or down through the table.

In your table view delegate, implement tableView:shouldSelectRow:. Do whatever you want, then return YES. It'll get triggered as you select items in the table view.

I'm not sure what you mean because when I select something in a table I can move up and down in the table using the arrow keys. But if you want to customize the behavior more I have a solution. In one of my apps I wanted to detect when the return or enter key was pressed and then perform some action accordingly. I created a new class and made it a subclass of NSWindow. In interface builder I set the main window to be this class. Then I override the keyDown: method of NSWindow in that subclass. So whenever my main window is frontmost (first responder) then key presses are detected and filtered through the method. I'm sure you could do something similar for arrow presses. You might want to make your class a subclass of NSTableView instead of NSWindow depending on how you want to catch the key presses. I wanted it to work for the entire application but you may want it to work only when the table view is first responder.
- (void)keyDown:(NSEvent *)theEvent {
if ([theEvent type] == NSKeyDown) {
NSString* characters = [theEvent characters];
if (([characters length] > 0) && (([characters characterAtIndex:0] == NSCarriageReturnCharacter) || ([characters characterAtIndex:0] == NSEnterCharacter))) {
// do something here when return or enter is pressed
}
}
}

aha! Did you accidentally BREAK NSTableView by doing this?
#implementation NSTableView ( DeleteKeyCategory )
-( void ) keyDown: ( NSEvent * ) event
{
// ... do something ...
[super keyDown:event];
}
#end
For me, this had the nefarious side effect of REPLACING NSTableView's keyDown: routine, and it broke the cursor keys. (kind of like swizzling)
Lessons I've learned:
- avoid the keyDown: routine altogether.
- subclassing Apple NSControls will save work in the long run.
This is the type of mistake that makes using NSTableView very frustrating.
Maybe Apple could detect this kind of thing in the static analyzer?

Arrows are used for selection, not for performing any Action. The action that will be applied to the selected item will usually be set by the "action" or "doubleAction" property of the TableView.
Clicking on a table-row does two different things.
TRIES to select the table row (sometimes the table-row can REFUSE to be selected, that's why there is a "shouldSelect" delegate method).
If new selection took place, then the action is performed (with the tableView as the sender). There you can ask the table about the current selection and do whatever you need with it.
Please consider the situation when there are SEVERAL selected rows, or selected columns, or many other complicated situations.
In your case --- what I would recommend, is that you implement the
selectionDidChange:(NSNotigivation)notification;
NSTableView delegate call. This is called AFTER selection has changed, and you know by then the new current selection, and do whatever you want with the selected items.

Related

Editable NSTextField in NSTableView

I have a view based NSTableView with some labels in my customized and subclassed view. One of the label should be editable, so therefore I set this NSTextField to editable.
But now I have two problems, I can't solve:
1) If I move the mouse over the editable NSTextField, the cursor don't change to the IBeamCursor (the edit cursor).
2) I need to double click at the label, to be able to edit. I want to have a single click. I found some solutions for this problem here at stackoverflow, the best one is to override the acceptsFirstResponder to return always true, but then, clicking at the NSTextField selects the whole text instead of placing the cursor at the clicked position.
Sorry... this is a duplicate. I found this:
NSTableView - select row and respond to mouse events immediately
You have to subclass NSTableView. My swift code:
class TableViewEditing: NSTableView {
...
override func validateProposedFirstResponder(responder: NSResponder, forEvent event: NSEvent?) -> Bool {
return true
}
}
EDIT:
Just one disadvantage: Sometimes entering the edit mode, it seems that the text is just shortly selected and deselected. But you can see, that this is a cocoa problem, it's the same for example in Apple reminders app.

Override key equivalent for NSButton in NSTextField

I have a window with two NSButtons and an NSTextField along with several views and several other controls. I assign the right and left arrows keys to each of the two NSButtons. The two buttons respond to the right and left arrow keys. However, when in the NSTextField, I want the arrow keys to perform as the normally would in a text field and not trigger the NSButtons. I have tried reading through the Cocoa Key Handling documentation and other questions regarding key events, but I could not find an example of trying change the key equivalent behavior in one control. I tried subclassing the NSTextField but couldn't trap the arrow keys. How can this be implemented?
You can override becomeFirstResponder: and in your implementation call setKeyEquivalent:. If you want to remove the key equivalent when the button loses first responder status, override resignFirstResponder:.
Do this in the control whose first-responder status you want to affect the button's equivalent. For example, if you have a view as a container and it can become first responder, you'd override -becomeFirstResponder: (calling super) then manage the button's equivalent there. If you don't yet understand these topics, you have a lot of prerequisite reading to do because a simple answer isn't possible here.
You could subclass NSButton and override performKeyEquivalent: like so:
override func performKeyEquivalent(event: NSEvent) -> Bool {
guard
let window = window where window.firstResponder.isKindOfClass(NSText.self) == false
else {
return false
}
return super.performKeyEquivalent(event)
}
This essentially disables the key equivalent if the first responder is a text field/view.

Cocoa: Avoiding 'Updates Continuously' in control binds

I have several panels that contain NSTextField controls bound to properties within the File's Owner object. If the user edits a field and then presses Tab, to move to the next field, it works as expected. However if the user doesn't press Tab and just presses the OK button, the new value is not set in the File's Owner object.
In order to workaround this I have set Updates Continuously in the binding, but this must be expensive (EDIT: or at least it's inelegant).
Is there a way to force the bind update when the OK button is pressed rather than using Updates Continuously?
You're right that you don't need to use the continuously updates value option.
If you're using bindings (which you are), then what you should be doing is calling the -commitEditing method of the NSController subclass that's managing the binding. You'd normally do this in your method that closes the sheet that you're displaying.
-commitEditing tells the controller to finish editing in the active control and commit the current edits to the bound object.
It's a good idea to call this whenever you are performing a persistence operation such as a save.
The solution to this is to 'end editing' in the action method that gets called by the OK button. As the pane is a subclass of NSWindowController, the NSWindow is easily accessible, however in your code you might have to get the NSWindow via a control you have bound to the controller; for example NSWindow *window = [_someControl window].
Below is the implementation of my okPressed action method.
In summary I believe this is a better solution to setting Updated Continuously in the bound controls.
- (IBAction)okPressed:(id)sender
{
NSWindow *window = [self window];
BOOL editingEnded = [window makeFirstResponder:window];
if (!editingEnded)
{
logwrn(#"Unable to end editing");
return;
}
if (_delegateRespondsToEditComplete)
{
[_delegate detailsEditComplete:&_mydetails];
}
}
Although this is really old, I absolutely disagree with the assumption that this question is based on.
Countinously updating the binding is absolutely not expensive. I guess you might think this updates the value continuously, understanding as "regularly based on some interval".
But this is not true. This just means it updates whenever you change the bound value. This means, when you type something in a textView, it would update as you write; this is what you'd want in this situation.

Change 'enter' key behaviour in NSTableView

I'm trying to create a simple data entry application for the Mac (not iPhone).
It has an NSTableView (inside an NSScrollView) which contains an NSTextField into which the user keys data, line by line.
What I have so far is working okay, except that in order to actually be able to type anything into the text field, you have to press the 'Enter' key first. Then when you press the 'Enter' key at the end, the field leaves 'edit' mode, but the highlight just sits on the same row. Having to press 'Enter', 'down arrow', 'Enter' between each row of data entered is not very efficient.
What I would prefer is that when you press the 'Enter' key, the cursor moves down to the next row and the field is automatically placed into 'edit' mode.
I had thought that such a thing should be fairly straightforward, but I have been tearing my hair out all evening without any progress, and I still don't even know which way I should be heading.
I tried looking for some attribute settings in the 'inspectors' in Xcode, but couldn't see anything.
Searching online, suggestions seemed to keep coming back to somehow using First Responder. Playing around with this, I've only managed to get the blue highlight ring to move down around my TableView on demand, but this still doesn't seem to put the text field into 'edit' mode nor move to another line.
Finally I found some information about subclassing and implementing your own textfields, but this seems like a very long and convoluted way to achieve this.
Can someone point me in the right direction as to how to do this?
Am I maybe using the wrong kind of control? Perhaps something else is more appropriate. Note that each line is distinct, so I don't want things like word wrap etc.
You were on the right track with the First Responder, but it's important to not that text editing is handled by the window's Field Editor (an instance of NSTextView), which becomes the first responder when text editing begins. The field editor informs its client (in this case your Table View) during editing.
The easiest way to handle this would be to subclass NSTableView and override its textDidEndEditing: method. Something like this should get you started:
- (void) textDidEndEditing:(NSNotification *)notification {
[super textDidEndEditing:notification];
int textMovement = [[notification.userInfo valueForKey:#"NSTextMovement"] intValue];
if (NSReturnTextMovement == textMovement) {
NSText *fieldEditor = notification.object;
// The row and column for the cell that just ended editing
NSInteger row = [self rowAtPoint:fieldEditor.frame.origin];
NSInteger col = [self columnAtPoint:fieldEditor.frame.origin];
if (++col >= self.numberOfColumns) {
col = 0;
if (++row >= self.numberOfRows) return;
}
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row]
byExtendingSelection:NO];
[self editColumn:col row:row withEvent:nil select:YES];
}
}
This will progress left-to-right, top-to-bottom through the table's columns and rows. Though you said you were using a NSTextField, I presume you meant you were using a NSTextFieldCell in a cell-based table. That's another important distinction. If you're not familiar with the concept of the field editor, I suggest you read up.

Why for only some actions must I call setTarget?

For most actions, I just click and drag in InterfaceBuilder to "wire up" a call from some interface object to my code. For example, if I want to know when the user single-clicks a row in a table, I drag a connection from the table's action to my controller's action.
But now let's consider the user double-clicking a row. If I want one of my actions to be called when this happens, I need to call not only -[NSTableView setDoubleAction] but also -[NSControl setTarget]. Why?
To be clear, I am not asking why Interface Builder doesn't support setDoubleAction. All tools have limitations. I am trying to gain a greater understanding about how and why setTarget doesn't seem to be necessary unless and until I want setDoubleAction to work. Another way to ask this question would be: Why don't I need to do anything in Interface Builder to set the target of the table's (single-click) action?
If you connect your table view to an action in IB, then call setDoubleAction on it, there should be no need to make an additional call to setTarget. However, if you only wish to receive the double click message, and you didn’t connect the table view to an action in IB, you will have to call setTarget.
A table view will send action and doubleAction to the same target. You can imagine NSTableView as being implemented like this:
#implementation NSTableView
- (void)theUserClickedOnMe
{
[self sendAction:[self action] to:[self target];
}
- (void)theUserDoubleClickedOnMe
{
[self sendAction:[self doubleAction] to:[self target]];
}
#end
And what you’re doing in IB is something like this:
- (void)userConnectedControl:(NSControl *)control
toAction:(SEL)action
ofObject:(id)object
{
[control setTarget:object];
[control setAction:action];
}
The real implementations are nowhere close to that, but that is effectively what’s going on.
If you set an action (or double-click action) and don't set a target (or set the target to nil), then the action message will go through the responder chain.
If you set a target in addition to an action, the action message will go only to that object.

Resources