Mac开发基础20-NSCollectionView

NSCollectionView 是 macOS 开发中的一种强大控件,类似于 iOS 上的 UICollectionView,用于展示和管理网格、列表等多种布局的数据展示视图。

1. 基本使用

创建和初始化

Objective-C

#import <Cocoa/Cocoa.h>

// 创建并初始化一个 NSCollectionView 实例
NSCollectionView *collectionView = [[NSCollectionView alloc] initWithFrame:NSMakeRect(0, 0, 400, 300)];

// 创建一个 NSCollectionView 流水布局(类似于 UICollectionView 的 UICollectionViewFlowLayout)
NSCollectionViewFlowLayout *layout = [[NSCollectionViewFlowLayout alloc] init];
layout.itemSize = NSMakeSize(100, 100); // 设置每个项目的大小
layout.sectionInset = NSEdgeInsetsMake(10, 10, 10, 10); // 设置每个节的内边距
layout.minimumInteritemSpacing = 10; // 行间距
layout.minimumLineSpacing = 10; // 列间距

// 设置 CollectionView 的布局
[collectionView setCollectionViewLayout:layout];

Swift

import Cocoa

// 创建并初始化一个 NSCollectionView 实例
let collectionView = NSCollectionView(frame: NSRect(x: 0, y: 0, width: 400, height: 300))

// 创建一个 NSCollectionView 流水布局(类似于 UICollectionView 的 UICollectionViewFlowLayout)
let layout = NSCollectionViewFlowLayout()
layout.itemSize = NSSize(width: 100, height: 100) // 设置每个项目的大小
layout.sectionInset = EdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // 设置每个节的内边距
layout.minimumInteritemSpacing = 10 // 行间距
layout.minimumLineSpacing = 10 // 列间距

// 设置 CollectionView 的布局
collectionView.collectionViewLayout = layout

数据源和委托

NSCollectionView 依赖数据源(NSCollectionViewDataSource)和委托(NSCollectionViewDelegate)来提供数据和处理用户交互事件。

Objective-C

// 设置数据源和委托
[collectionView setDataSource:self];
[collectionView setDelegate:self];
// 实现数据源协议
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
    return 1; // 返回节数
}

- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return _dataArray.count; // 返回每个节中的项目数
}

- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
    NSCollectionViewItem *item = [collectionView makeItemWithIdentifier:@"ItemIdentifier" forIndexPath:indexPath];
    item.textField.stringValue = _dataArray[indexPath.item];
    return item; // 返回对应的项目
}
// 实现委托协议
- (void)collectionView:(NSCollectionView *)collectionView didSelectItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths {
    NSIndexPath *selectedIndexPath = indexPaths.allObjects.firstObject;
    NSLog(@"Selected item at section: %ld, item: %ld", selectedIndexPath.section, selectedIndexPath.item); // 处理选择事件
}

Swift

// 设置数据源和委托
collectionView.dataSource = self
collectionView.delegate = self
// 实现数据源协议
func numberOfSections(in collectionView: NSCollectionView) -> Int {
    return 1 // 返回节数
}

func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
    return dataArray.count // 返回每个节中的项目数
}

func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
    let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("ItemIdentifier"), for: indexPath)
    item.textField?.stringValue = dataArray[indexPath.item]
    return item // 返回对应的项目
}
// 实现委托协议
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
    if let selectedIndexPath = indexPaths.first {
        print("Selected item at section: \(selectedIndexPath.section), item: \(selectedIndexPath.item)") // 处理选择事件
    }
}

项目注册和重用

Objective-C

// 注册项目类
[collectionView registerNib:[[NSNib alloc] initWithNibNamed:@"MyCollectionViewItem" bundle:nil] forItemWithIdentifier:@"ItemIdentifier"];

Swift

// 注册项目类
let nib = NSNib(nibNamed: "MyCollectionViewItem", bundle: nil)
collectionView.register(nib, forItemWithIdentifier: NSUserInterfaceItemIdentifier("ItemIdentifier"))

2. 编辑和选择

允许选择项目

Objective-C

// 允许选择项目
[collectionView setAllowsSelection:YES];
[collectionView setAllowsMultipleSelection:YES]; // 允许多选

Swift

// 允许选择项目
collectionView.allowsSelection = true
collectionView.allowsMultipleSelection = true // 允许多选

自定义项目视图和布局

Objective-C

@interface CustomCollectionViewItem : NSCollectionViewItem
@end

@implementation CustomCollectionViewItem

- (void)viewDidLoad {
    [super viewDidLoad];
    // 自定义视图的配置
    self.view.wantsLayer = YES;
    self.view.layer.backgroundColor = [NSColor lightGrayColor].CGColor;
}

@end

Swift

class CustomCollectionViewItem: NSCollectionViewItem {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 自定义视图的配置
        self.view.wantsLayer = true
        self.view.layer?.backgroundColor = NSColor.lightGray.cgColor
    }
}

3. 高级用法

动态内容的添加和删除

Objective-C

- (void)addItem {
    [_dataArray addObject:@"New Item"];
    [collectionView reloadData]; // 添加新项目并刷新数据
}

- (void)removeItemAtIndexPath:(NSIndexPath *)indexPath {
    [_dataArray removeObjectAtIndex:indexPath.item];
    [collectionView deleteItemsAtIndexPaths:[NSSet setWithObject:indexPath]]; // 移除项目并刷新数据
}

Swift

func addItem() {
    dataArray.append("New Item")
    collectionView.reloadData() // 添加新项目并刷新数据
}

func removeItem(at indexPath: IndexPath) {
    dataArray.remove(at: indexPath.item)
    collectionView.deleteItems(at: [indexPath]) // 移除项目并刷新数据
}

项目拖放操作

Objective-C

// 开始拖放时
- (BOOL)collectionView:(NSCollectionView *)collectionView canDragItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths withEvent:(NSEvent *)event {
    return YES; // 允许拖放
}

- (NSArray<NSPasteboardWriting> *)collectionView:(NSCollectionView *)collectionView pasteboardWriterForItemAtIndexPath:(NSIndexPath *)indexPath {
    return @[[NSNumber numberWithInteger:indexPath.item]]; // 将项目索引写入剪贴板
}

// 验证拖放
- (NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id<NSDraggingInfo>)draggingInfo proposedIndexPath:(NSIndexPath *__autoreleasing  _Nullable *)proposedDropIndexPath dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation {
    return NSDragOperationMove; // 允许移动操作
}

// 接受拖放
- (BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id<NSDraggingInfo>)draggingInfo indexPath:(NSIndexPath *)indexPath dropOperation:(NSCollectionViewDropOperation)dropOperation {
    NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
    NSString *itemIndexString = [pasteboard stringForType:NSPasteboardTypeString];
    NSInteger fromIndex = [itemIndexString integerValue];
    
    // 移动项目并更新数据
    id movingItem = _dataArray[fromIndex];
    [_dataArray removeObjectAtIndex:fromIndex];
    [_dataArray insertObject:movingItem atIndex:indexPath.item];
    
    [collectionView reloadData]; // 刷新数据
    return YES;
}

Swift

// 开始拖放时
func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
    return true // 允许拖放
}

func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
    return NSNumber(value: indexPath.item) // 将项目索引写入剪贴板
}

// 验证拖放
func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath: AutoreleasingUnsafeMutablePointer<IndexPath>, dropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
    return .move // 允许移动操作
}

// 接受拖放
func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
    guard let itemIndexString = draggingInfo.draggingPasteboard.string(forType: .string),
          let fromIndex = Int(itemIndexString) else {
        return false
    }
    
    // 移动项目并更新数据
    let movingItem = dataArray[fromIndex]
    dataArray.remove(at: fromIndex)
    dataArray.insert(movingItem, at: indexPath.item)
    
    collectionView.reloadData() // 刷新数据
    return true
}

自定义节头视图和脚视图

Objective-C

// 自定义节头视图
@interface CustomHeaderView : NSView
@property (nonatomic, strong) NSTextField *headerLabel;
@end

@implementation CustomHeaderView

- (instancetype)initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:frameRect];
    if (self) {
        _headerLabel = [[NSTextField alloc] initWithFrame:self.bounds];
        [_headerLabel setBezeled:NO];
        [_headerLabel setDrawsBackground:NO];
        [_headerLabel setEditable:NO];
        [_headerLabel setSelectable:NO];
        [self addSubview:_headerLabel];
    }
    return self;
}

@end

// 返回自定义节头视图
- (NSView *)collectionView:(NSCollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if (kind == NSCollectionElementKindSectionHeader) {
        CustomHeaderView *headerView = [collectionView makeSupplementaryViewOfKind:kind withIdentifier:@"HeaderViewIdentifier" forIndexPath:indexPath];
        headerView.headerLabel.stringValue = @"Section Header";
        return headerView; // 返回自定义节头视图
    }
    return nil;
}

Swift

// 自定义节头视图
class CustomHeaderView: NSView {
    var headerLabel: NSTextField!

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        headerLabel = NSTextField(frame: self.bounds)
        headerLabel.isBezeled = false
        headerLabel.drawsBackground = false
        headerLabel.isEditable = false
        headerLabel.isSelectable = false
        addSubview(headerLabel)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// 返回自定义节头视图
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
    if kind == NSCollectionView.elementKindSectionHeader {
        let headerView = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: NSUserInterfaceItemIdentifier("HeaderViewIdentifier"), for: indexPath) as! CustomHeaderView
        headerView.headerLabel.stringValue = "Section Header"
        return headerView // 返回自定义节头视图
    }
    return NSView()
}

封装工具类

为了更方便地使用 NSCollectionView,我们可以封装一个工具类,提供常见功能的高层接口。

Objective-C

#import <Cocoa/Cocoa.h>

@interface NSCollectionViewHelper : NSObject

+ (NSCollectionView *)createCollectionViewWithFrame:(NSRect)frame itemIdentifiers:(NSArray<NSString *> *)itemIdentifiers layout:(NSCollectionViewLayout *)layout target:(id)target;
+ (void)setupDragAndDropForCollectionView:(NSCollectionView *)collectionView;
+ (void)registerHeaderViewForCollectionView:(NSCollectionView *)collectionView withIdentifier:(NSString *)identifier;

@end

@implementation NSCollectionViewHelper

+ (NSCollectionView *)createCollectionViewWithFrame:(NSRect)frame itemIdentifiers:(NSArray<NSString *> *)itemIdentifiers layout:(NSCollectionViewLayout *)layout target:(id)target {
    NSCollectionView *collectionView = [[NSCollectionView alloc] initWithFrame:frame];
    collectionView.collectionViewLayout = layout;

    for (NSString *identifier in itemIdentifiers) {
        [collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:identifier];
    }

    collectionView.dataSource = target;
    collectionView.delegate = target;
    return collectionView;
}

+ (void)setupDragAndDropForCollectionView:(NSCollectionView *)collectionView {
    [collectionView registerForDraggedTypes:@[NSPasteboardTypeString]];
    collectionView.draggingSourceOperationMask = NSDragOperationEvery;
}

+ (void)registerHeaderViewForCollectionView:(NSCollectionView *)collectionView withIdentifier:(NSString *)identifier {
    [collectionView registerClass:[CustomHeaderView class] forSupplementaryViewOfKind:NSCollectionElementKindSectionHeader withIdentifier:identifier];
}

@end

Swift

import Cocoa

class NSCollectionViewHelper {
    
    // 创建 CollectionView 并注册项目标识符
    static func createCollectionView(frame: NSRect, itemIdentifiers: [String], layout: NSCollectionViewLayout, target: AnyObject) -> NSCollectionView {
        let collectionView = NSCollectionView(frame: frame)
        collectionView.collectionViewLayout = layout
        
        for identifier in itemIdentifiers {
            collectionView.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier))
        }
        
        collectionView.dataSource = target as? NSCollectionViewDataSource
        collectionView.delegate = target as? NSCollectionViewDelegate
        return collectionView
    }
    
    // 配置拖放功能
    static func setupDragAndDrop(for collectionView: NSCollectionView) {
        collectionView.registerForDraggedTypes([.string])
        collectionView.draggingSourceOperationMask = .every
    }
    
    // 注册节头视图
    static func registerHeaderView(for collectionView: NSCollectionView, withIdentifier identifier: String) {
        collectionView.register(CustomHeaderView.self, forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier))
    }
}

使用示例

Objective-C

// 创建 CollectionView
NSCollectionViewFlowLayout *layout = [[NSCollectionViewFlowLayout alloc] init];
layout.itemSize = NSMakeSize(100, 100);

NSCollectionView *collectionView = [NSCollectionViewHelper createCollectionViewWithFrame:NSMakeRect(0, 0, 400, 300)
                                                                         itemIdentifiers:@[@"ItemIdentifier"]
                                                                                  layout:layout
                                                                                  target:self];

// 配置拖放功能
[NSCollectionViewHelper setupDragAndDropForCollectionView:collectionView];

// 注册节头视图
[NSCollectionViewHelper registerHeaderViewForCollectionView:collectionView withIdentifier:@"HeaderViewIdentifier"];

Swift

// 创建 CollectionView
let layout = NSCollectionViewFlowLayout()
layout.itemSize = NSSize(width: 100, height: 100)

let collectionView = NSCollectionViewHelper.createCollectionView(frame: NSRect(x: 0, y: 0, width: 400, height: 300),
                                                                 itemIdentifiers: ["ItemIdentifier"],
                                                                 layout: layout,
                                                                 target: self)

// 配置拖放功能
NSCollectionViewHelper.setupDragAndDrop(for: collectionView)

// 注册节头视图
NSCollectionViewHelper.registerHeaderView(for: collectionView, withIdentifier: "HeaderViewIdentifier")

总结

通过了解这些常见 API 和基础技巧,以及一些高级用法和封装工具类,可以更高效地使用 NSCollectionView 实现复杂的数据展示和用户交互。在开发复杂的 macOS 应用时,熟练掌握 NSCollectionView 的使用将大大提高工作效率。

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