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 和基礎技巧,可以實現基本的表格展示和互動需求。