My app has an NSOutlineView
and an NSTableView
, and I'm having the same problem with both. With a row in either selected, pressing the tab key puts the first column into edit mode instead of making the next key view first responder. To get to the next key view, you need to tab through all of the columns.
Also, shift-tabbing into either view results in the last column going into edit mode, necessitating more shift-tabs to get into its previous key view.
In case it matters, I'm using the autocalculated key view loop, not my own, with my NSWindow
set to autorecalculatesKeyViewLoop = YES
. I would like tabbing between the columns once the user elects to edit a column, but I don't think it's standard behavior for the tab key to trigger edit mode.
Update
Thanks to the helpful responses below, I worked it out. Basically, I override -keyDown
in my custom table view class, which handles tabbing and shift-tabbing out of the table view. It was tougher to solve shift-tabbing into the table view, however. I set a boolean property to YES
in the custom table view's -acceptsFirstResponder
if it's accepting control from another view.
The delegate's -tableView:shouldEditTableColumn:row
checks for that when the current event is a shift-tab keyDown
event. -tableView:shouldEditTableColumn:row
is called and it's not a shift-tab event, it sets the table view's property back to NO
so it can still be edited as usual.
I've pasted the full solution below.
/* CustomTableView.h */
@interface CustomTableView : NSTableView {}
@property (assign) BOOL justFocused;
@end
/* CustomTableView.m */
@implementation CustomTableView
@synthesize justFocused;
- (BOOL)acceptsFirstResponder {
if ([[self window] firstResponder] != self) {
justFocused = YES;
}
return YES;
}
- (void)keyDown:(NSEvent *)theEvent
{
// Handle the Tab key
if ([[theEvent characters] characterAtIndex:0] == NSTabCharacter) {
if (([theEvent modifierFlags] & NSShiftKeyMask) != NSShiftKeyMask) {
[[self window] selectKeyViewFollowingView:self];
} else {
[[self window] selectKeyViewPrecedingView:self];
}
}
else {
[super keyDown:theEvent];
}
}
@end
/* TableViewDelegate.m */
. . .
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
{
NSEvent *event = [NSApp currentEvent];
BOOL shiftTabbedIn = ([event type] == NSKeyDown
&& [[event characters] characterAtIndex:0] == NSBackTabCharacter);
if (shiftTabbedIn && ((CustomTableView *)tableView).justFocused == YES) {
return NO;
} else {
((CustomTableView *)tableView).justFocused = NO;
}
return YES;
}
. . .
The infrastructure for drawing, printing, and handling events in an app. macOS 10.0+
AppKit contains the objects you need to build the user interface for a macOS app. In addition to drawing windows, buttons, panels, and text fields, it handles all the event management and interaction between your app, people, and macOS.
This is the default behavior. If there's no row selected, the table view as a whole has focus, and the Tab key switches to the next key view. If there is a row selected, the table view begins editing or moves to the next cell if already editing.
From AppKit Release Notes:
Tables now support inter-cell navigation as follows:
- Tabbing forward to a table focuses the entire table.
- Hitting Space will attempt to 'performClick:' on a NSButtonCell in the selected row, if there is only one instance in that row.
- Tabbing again focuses the first "focusable" (1) cell, if there is one.
- If the newly focused cell can be edited, editing will begin.
- Hitting Space calls 'performClick:' on the cell and sets the datasource value afterwards, if changed. (2)
- If a text cell is editing, hitting Enter will commit editing and focus will be returned to the tableview, and Tab/Shift-tab will commit the editing and then perform the new tab-loop behavior.
- Tabbing will only tab through a single row
- Once the last cell in a row is reached, tab will take the focus to the next focusable control.
- Back tabbing into a table will select the last focusable cell.
If you want to change this behavior, the delegate method tableView:shouldEditTableColumn:row:
may be helpful. You may also have to subclass NSTableView
if you really want to affect only the behavior of the Tab key.
The solution using keyDown
didn't work for me. Perhaps because it is for cell-based table view.
My solution for a view-based table view, in Swift, looks like this:
extension MyTableView: NSTextFieldDelegate {
func controlTextDidEndEditing(_ obj: Notification) {
guard
let view = obj.object as? NSView,
let textMovementInt = obj.userInfo?["NSTextMovement"] as? Int,
let textMovement = NSTextMovement(rawValue: textMovementInt) else { return }
let columnIndex = column(for: view)
let rowIndex = row(for: view)
let newRowIndex: Int
switch textMovement {
case .tab:
newRowIndex = rowIndex + 1
if newRowIndex >= numberOfRows { return }
case .backtab:
newRowIndex = rowIndex - 1
if newRowIndex < 0 { return }
default: return
}
DispatchQueue.main.async {
self.editColumn(columnIndex, row: newRowIndex, with: nil, select: true)
}
}
}
You also need to set the cell.textField.delegate
so that the implementation works.
My blog post on this tricky workaround: https://samwize.com/2018/11/13/how-to-tab-to-next-row-in-nstableview-view-based-solution/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With