NSTableView
是 macOS 应用程序中用于显示和管理数据表格的控件。它提供了丰富的 API 和高度自定义的能力,使得开发者可以精细地控制表格的显示和行为。本文将详细介绍 NSTableView
的常见 API 和一些基础技巧,并深入探讨其相关知识。
1. 基本使用
创建和初始化
Objective-C
#import <Cocoa/Cocoa.h>
// 创建并初始化一个 NSTableView 实例
NSTableView *tableView = [[NSTableView alloc] initWithFrame:NSMakeRect(0, 0, 400, 300)];
// 创建并初始化一个 NSTableColumn 实例
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"ColumnIdentifier"];
[column setWidth:300]; // 设置列宽
// 为表格视图添加列
[tableView addTableColumn:column];
// 设置表格头标题
[[column headerCell] setStringValue:@"Header Title"];
Swift
import Cocoa
// 创建并初始化一个 NSTableView 实例
let tableView = NSTableView(frame: NSRect(x: 0, y: 0, width: 400, height: 300))
// 创建并初始化一个 NSTableColumn 实例
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("ColumnIdentifier"))
column.width = 300 // 设置列宽
// 为表格视图添加列
tableView.addTableColumn(column)
// 设置表格头标题
column.headerCell.title = "Header Title"
数据源和委托
NSTableView
依赖数据源 (NSTableViewDataSource
) 和委托 (NSTableViewDelegate
) 来提供数据和处理用户交互事件。
Objective-C
// 设置数据源和委托
[tableView setDataSource:self];
[tableView setDelegate:self];
// 实现数据源协议
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return _dataArray.count; // 返回数据源中的行数
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row {
return _dataArray[row]; // 返回行数据
}
// 实现委托协议
- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row {
NSTextField *textField = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if (!textField) {
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 20)];
textField.identifier = tableColumn.identifier;
}
textField.stringValue = _dataArray[row];
return textField;
}
Swift
// 设置数据源和委托
tableView.dataSource = self
tableView.delegate = self
// 实现数据源协议
func numberOfRows(in tableView: NSTableView) -> Int {
return dataArray.count // 返回数据源中的行数
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return dataArray[row] // 返回行数据
}
// 实现委托协议
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let identifier = tableColumn?.identifier ?? NSUserInterfaceItemIdentifier("")
var textField = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTextField
if textField == nil {
textField = NSTextField(frame: NSRect(x: 0, y: 0, width: tableColumn?.width ?? 100, height: 20))
textField?.identifier = identifier
}
textField?.stringValue = dataArray[row]
return textField
}
2. 编辑和选择
允许选择行
Objective-C
[tableView setAllowsSelection:YES]; // 允许选择行
Swift
tableView.allowsSelection = true // 允许选择行
允许编辑单元格
Objective-C
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return YES; // 允许编辑单元格
}
Swift
func tableView(_ tableView: NSTableView, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
return true // 允许编辑单元格
}
处理行选择事件
Objective-C
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
NSInteger selectedRow = [tableView selectedRow];
if (selectedRow != -1) {
NSLog(@"Selected row: %ld", (long)selectedRow); // 处理行选择事件
}
}
Swift
func tableViewSelectionDidChange(_ notification: Notification) {
let selectedRow = tableView.selectedRow
if selectedRow != -1 {
print("Selected row: \(selectedRow)") // 处理行选择事件
}
}
3. 高级用法
自定义单元格
可以通过创建自定义 NSTableCellView
来实现复杂的单元格布局。
Objective-C
@interface CustomTableCellView : NSTableCellView
@property (nonatomic, strong) NSTextField *customTextField;
@end
@implementation CustomTableCellView
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_customTextField = [[NSTextField alloc] initWithFrame:frameRect];
[self addSubview:_customTextField];
}
return self;
}
@end
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
CustomTableCellView *cellView = [tableView makeViewWithIdentifier:@"CustomCell" owner:self];
if (!cellView) {
cellView = [[CustomTableCellView alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 20)];
cellView.identifier = @"CustomCell";
}
cellView.customTextField.stringValue = _dataArray[row];
return cellView;
}
Swift
class CustomTableCellView: NSTableCellView {
var customTextField: NSTextField?
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
customTextField = NSTextField(frame: frameRect)
if let customTextField = customTextField {
addSubview(customTextField)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("CustomCell"), owner: self) as? CustomTableCellView
if cellView == nil {
cellView = CustomTableCellView(frame: NSRect(x: 0, y: 0, width: tableColumn?.width ?? 100, height: 20))
cellView?.identifier = NSUserInterfaceItemIdentifier("CustomCell")
}
cellView?.customTextField?.stringValue = dataArray[row]
return cellView
}
表头的自定义视图
可以通过提供自定义表头视图来替换默认的表头视图。
Objective-C
- (NSTableHeaderView *)tableView:(NSTableView *)tableView viewForHeaderInSection:(NSInteger)section {
CustomHeaderView *headerView = [tableView makeViewWithIdentifier:@"CustomHeader" owner:self];
if (!headerView) {
headerView = [[CustomHeaderView alloc] initWithFrame:NSMakeRect(0, 0, tableView.frame.size.width, 30)];
headerView.identifier = @"CustomHeader";
}
headerView.title = @"Custom Header";
return headerView;
}
Swift
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var headerView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("CustomHeader"), owner: self) as? CustomHeaderView
if headerView == nil {
headerView = CustomHeaderView(frame: NSRect(x: 0, y: 0, width: tableView.frame.size.width, height: 30))
headerView?.identifier = NSUserInterfaceItemIdentifier("CustomHeader")
}
headerView?.title = "Custom Header"
return headerView
}
处理拖放操作
可以通过实现 tableView(_:writeRowsWith:to:)
和 tableView(_:acceptDrop:row:dropOperation:)
方法来支持拖放操作。
Objective-C
- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard {
// 允许拖动的行,并将相关数据写入到剪贴板
[pboard declareTypes:@[NSPasteboardTypeString] owner:self];
[pboard setString:[NSString stringWithFormat:@"%ld", rowIndexes.firstIndex] forType:NSPasteboardTypeString];
return YES;
}
- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation {
return NSDragOperationMove; // 指示允许移动操作
}
- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation {
NSPasteboard *pboard = [info draggingPasteboard];
NSString *rowString = [pboard stringForType:NSPasteboardTypeString];
NSInteger draggedRow = [rowString integerValue];
// 交换数据源中的行顺序
id draggedObject = _dataArray[draggedRow];
[_dataArray removeObjectAtIndex:draggedRow];
[_dataArray insertObject:draggedObject atIndex:row];
[tableView reloadData];
return YES;
}
Swift
func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
// 允许拖动的行,并将相关数据写入到剪贴板
pboard.declareTypes([.string], owner: self)
pboard.setString("\(rowIndexes.first ?? 0)", forType: .string)
return true
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
return .move // 指示允许移动操作
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
guard let rowString = info.draggingPasteboard.string(forType: .string),
let draggedRow = Int(rowString) else {
return false
}
// 交换数据源中的行顺序
let draggedObject = dataArray[draggedRow]
dataArray.remove(at: draggedRow)
dataArray.insert(draggedObject, at: row)
tableView.reloadData()
return true
}
4. 深入探讨
NSTableView
的内部机制
NSTableView
是建立在 NSView
之上的,它通过 NSTableColumn
管理列信息,并使用 rowHeight
属性控制行高。每个单元格作为 NSView
存在,可以是一个简单的 NSTextField
,也可以是自定义的 NSTableCellView
。
数据缓存和复用机制
NSTableView
使用了一种类似于 UITableView
的数据缓存和复用机制。通过 dequeueReusableCell(withIdentifier:)
方法,可以重用已经创建的单元格视图,提高性能。
性能优化策略
- 避免过多创建视图:尽量重用现有视图,减少不必要的视图创建。
- 接受部分更新:使用
reloadRows(atIndexes:)
方法更新特定行的数据,而不是reloadData
。 - 异步数据加载:对于大型数据集,可以使用异步加载来提高性能。例如,分页加载或后台加载数据。
总结
NSTableView
是一个强大且灵活的表格控件,通过了解其常见 API 和基础技巧,可以实现基本的表格展示和交互需求。