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
的使用将大大提高工作效率。