Enabling Row Selection and User Actions
Displaying a list of data in a table view is of little use if the user can’t select the rows. Using the methods of the NSTableView
class, you let users select one or more rows, drag to change a selection, and type to select. In addition, you can enable programmatic selection and display a contextual menu that’s associated with the table.
Getting the Current Row Selection
The NSTableView
class provides several methods for getting information about a table view’s currently selected rows: selectedRowIndexes
, selectedRow
, numberOfSelectedRows
, and isRowSelected:
.
The selectedRowIndexes
method provides the full and correct selection whether a single item is selected or multiple items are selected. The method returns an NSIndexSet
object containing the indexes of the selected rows.
The selectedRow
method returns the index of only the last selected row, or -1 if no row is selected. Using this method is the easiest way to get the current selection when multiple selection is disabled.
The numberOfSelectedRows
method, which returns the number of selected rows, can be used as a shortcut to determine whether any objects are selected when empty selection is permitted. Its value can also be used to determine whether user interface items should be enabled or disabled if they depend upon multiple items being selected in the table view. For example, if the numberOfSelectedRows
is greater than 1, you can disable the portions of the user interface that are relevant only when a single item is selected.
The isRowSelected:
method allows an app to find out whether a row at a specific index is selected.
Apps often need to iterate over the selected rows. You can implement this by iterating over the contents of the selection returned by selectedRowIndexes
or by using the NSIndexSet
block enumeration method enumerateIndexesUsingBlock:
. For more information about using these techniques, see Iterating Through Index Sets in Collections Programming Topics.
Responding to Changes in Row Selection
Users can select multiple rows using the Shift key (for continuous selections) or the Command key (for non-contiguous selections). For more information about user selection actions, see Make User Input Convenient in OS X Human Interface Guidelines.
As the user selects rows by dragging (using a trackpad or mouse), delegate methods can be informed of the changes in row selection. The delegate receives the following messages as changes in the selection occurs by the user. (Delegates for table views that are managed by Cocoa bindings also receive these messages and can act on them as required.) An app can receive these messages on a continuous basis, so it’s important to create efficient implementations.
selectionShouldChangeInTableView:
This method is usually implemented when it’s necessary to perform validation on editing of a row before allowing the row selection to change.
For example, if the user is editing a row in the table view and the content of that row doesn’t meet the required criteria, the delegate should return
NO
from this method to prevent the user from changing the selection. If the action was denied, this method is responsible for telling users why the selection change was disallowed and what action they can take to rectify the situation. By default, this method returnsYES
, which allows the selection to be changed.This method informs the delegate that the row selection is about to change due to interaction with the mouse or trackpad. Keyboard selection does not invoke this method.
This method informs the delegate that the row selection has changed and that, effectively, the table view selection action is completed. The method is sent to the delegate when the user releases the mouse button, whether selection is limited to a single row or multiple selection is enabled.
The tableViewSelectionIsChanging:
and tableViewSelectionDidChange:
messages are notifications. Each is passed an NSNotification
object. Sending the notification instance an object
message returns the table view relevant to the selection change.
A table view delegate can also implement the tableView:selectionIndexesForProposedSelection:
delegate method to allow or disallow the selection of a specific set of rows in a table view. The method is passed the indexes of the rows that are proposed to be selected and it returns the rows that will actually be selected. Implementing this method allows the app to selectively allow and disallow row selection as appropriate. For example, if the user clicks in an area of the table row that shouldn’t trigger selection, this method can be used to prevent that selection from taking place.
Allowing Multiple and Empty Selection
A table view can be configure selection in three ways:
Allow a single row to be selected at once
Allow multiple rows to be selected simultaneously
Attempt to prevent there from being an empty selection (that is, at least one row is always selected)
These attributes can be configured in Interface Builder or programmatically.
Programmatically, the table view methods for enabling and disabling these options are set using the following methods: setAllowsMultipleSelection:
and setAllowsEmptySelection:
.
Selecting and Deselecting Rows Programmatically
To select rows programmatically, the NSTableView
class provides the selectRowIndexes:byExtendingSelection:
instance method.
The selectRowIndexes:byExtendingSelection:
method expects an NSIndexSet
containing the indexes (zero-based) of the rows to be selected, and a parameter that specifies whether the current selection should be extended. If the extending selection parameter is YES
, the specified row indexes are selected in addition to any previously selected rows; if it’s NO
, the selection is changed to the newly specified rows. When this method is called, the delegate receives only the tableViewSelectionDidChange:
notification.
To deselect a row, pass the index of the row to deselect to deselectRow:
. When this method is called the delegate receives only the tableViewSelectionDidChange:
notification.
There are also two convenience methods that allow the selection and deselection of all the items in the table view. In general, these methods are connected to the user interface so that users can select or deselect all items in a table. The deselectAll:
method deselects all the selected rows, but only if allowsEmptySelection
returns YES
. Similarly, selectAll:
selects all the table view rows, but only if allowsMultipleSelection
returns YES
. Unlike the other programmatic methods, these two methods call selectionShouldChangeInTableView:
on the delegate object, if implemented, followed by tableViewSelectionDidChange:
. If either deselectAll:
or selectAll:
is called without the proper allows...
setting, it is ignored.
Using Type Selection to Select Rows
To simplify navigation in tables or to allow a user to select items using the keyboard, a table view can support type selection. Using type selection, a user types the first few letters of an entry and the table view content is searched for a matching row. You can use the setAllowsTypeSelect:
method to enable or disable type selection (by default, type selection is enabled).
Type selection uses the delegate method tableView:typeSelectStringForTableColumn:row:
to figure out which rows to match against. This delegate method returns a string that type selection uses for matching.
When the tableView:typeSelectStringForTableColumn:row:
method is not implemented by the delegate, the behavior is to call preparedCellAtColumn:row:
, passing the column index and row and then returning the stringValue
. This allows type selection to work on the values in any column and row in the table view. To restrict type selection to specific parts of the table, return nil
for columns that should be excluded. For example, the following method implementation allows type selection to match values only in the “name” column.
- (NSString *)tableView:(NSTableView *)tableView typeSelectStringForTableColumn:(NSTableColumn *)tableColumn |
row:(NSInteger)row |
{ |
if ([[tableColumn identifier] isEqualToString:@"name"]) |
{ |
NSUInteger tableColumnIndex=[[tableView tableColumns] indexOfObject:tableColumn]; |
return [[tableView preparedCellAtColumn:tableColumnIndex |
row:row] stringValue]; |
} |
return nil; |
} |
Implementing the delegate method tableView:nextTypeSelectMatchFromRow:toRow:forString:
allows the delegate to further customize type selection to match only a specific range of rows. This method is passed the current type selection string, and it compares the appropriate values within the selected rows, returning the row to select, or -1 if no row matches.
Finally, the tableView:shouldTypeSelectForEvent:withCurrentSearchString:
method gives the delegate the option to allow or disallow type selection for a particular keyboard event. You can implement this method to prevent certain characters from causing type selection. Note that returning NO
from this method prevents the specified event from being used for type selection; it doesn’t prevent standard event processing. Don’t use tableView:shouldTypeSelectForEvent:withCurrentSearchString:
to handle your own keyboard shortcuts. If you need to provide custom keyboard shortcut handling, override keyDown:
instead.
Displaying a Contextual Menu in a Table
You can use a contextual menu to offer users a convenient way to access a small set of commands that act on one or more items in a table. For example, when users Command-click a song listed in iTunes, a contextual menu appears that makes it easy to (among other things) play, copy, or delete the song.
An easy way to add a contextual menu to a table is to set the menu
outlet of the table to an NSMenu
object. And if you want to customize the contextual menu, you set an appropriate object as the menu’s delegate and implement the menuWillOpen:
method to customize the menu before it appears.
In the action method for a menu item, determine whether the clicked table row is in the set of indexes returned by selectedRowIndexes
. If it is, apply the action to all indexes in the set; otherwise, apply the action only to the clicked row. Here’s a convenience method that checks the selected row indexes and returns the set of indexes the action method should process:
- (NSIndexSet *)_indexesToProcessForContextMenu { |
NSIndexSet *selectedIndexes = [_tableViewMain selectedRowIndexes]; |
// If the clicked row is in selectedIndexes, then process all selectedIndexes. Otherwise, process only clickedRow. |
if ([_tableViewMain clickedRow] != -1 && ![selectedIndexes containsIndex:[_tableViewMain clickedRow]]) { |
selectedIndexes = [NSIndexSet indexSetWithIndex:[_tableViewMain clickedRow]]; |
} |
return selectedIndexes; |
} |
To see an example that uses the _indexesToProcessForContextMenu
method, download the TableViewPlayground: Using View-Based NSTableView and NSOutlineView sample project and look at the mnuRevealInFinderSelected:
method in ATComplexTableViewController.m
.
Specifying How Subviews Should Respond to Events
Views or controls in a table sometimes need to respond to incoming events. To determine whether a particular subview should receive the current mouse event, a table view calls validateProposedFirstResponder:forEvent:
in its implementation of hitTest
. If you create a table view subclass, you can override validateProposedFirstResponder:forEvent:
to specify which views can become the first responder. In this way, you receive mouse events.
The default NSTableView
implementation of validateProposedFirstResponder:forEvent:
uses the following logic:
Return
YES
for all proposed first responder views unless they are instances or subclasses ofNSControl
.Determine whether the proposed first responder is an
NSControl
instance or subclass.If the control is an
NSButton
object, returnYES
.If the control is not an
NSButton
, call the control’shitTestForEvent:inRect:ofView:
to see whether the hit area is trackable (that is,NSCellHitTrackableArea
) or is an editable text area (that is,NSCellHitEditableTextArea
), and return the appropriate value. Note that if a text area is hit,NSTableView
also delays the first responder action.
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-07-15