Mac开发基础18-NSTableView(一)

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:) 方法,可以重用已经创建的单元格视图,提高性能。

性能优化策略

  1. 避免过多创建视图:尽量重用现有视图,减少不必要的视图创建。
  2. 接受部分更新:使用 reloadRows(atIndexes:) 方法更新特定行的数据,而不是 reloadData
  3. 异步数据加载:对于大型数据集,可以使用异步加载来提高性能。例如,分页加载或后台加载数据。

总结

NSTableView 是一个强大且灵活的表格控件,通过了解其常见 API 和基础技巧,可以实现基本的表格展示和交互需求。

posted @ 2024-08-06 17:06  Mr.陳  阅读(59)  评论(0编辑  收藏  举报