OS X系统 Drag & Drop

来源:互联网 发布:好桌道类似软件 编辑:程序博客网 时间:2024/05/10 11:35

Drag & Drop(拖拽)提供了在应用与OS X系统,不同应用之间,应用内部多种场景下 资源,文件,数据可视化交换的极致的一种用户体验。

我们把可以拖拽的视图(view)或窗口(window)称为 拖放源(Drag Sources),接收拖放的视图或窗口称为 拖放目标(Drag Destination)。

DragDrop

拖放开始时会出现代表拖放源Drag Sources的图标顺着鼠标轨迹运动,直到拖放目标Drag Destination 接收了这个拖放请求,就完成了一次成功的拖放。如果拖放目标不能响应这个拖放请求,代表拖放源Drag Sources的图标会以动画形式弹回到拖放源以前的位置。

拖放源和拖放目标之间数据交换是通过使用系统的剪贴板(NSPasteboard)保存数据来完成的。
DragDrop2

整个拖放过程中,涉及到拖放源和拖放目标之间一系列的交互处理流程。

拖放开始

用户鼠标点击NSView/NSWindow触发MouseDown事件,调用beginDraggingSessionWithItems方法开始建立一个拖放的session,开始启动拖放过程。

beginDraggingSessionWithItems需要3个参数,按顺序依次为拖放数据items,鼠标的NSEvent,拖放源代理。

  • (void)mouseDown:(NSEvent *)theEvent
    {

    NSMutableArray *draggingItems = [NSMutableArray array];

    NSPasteboardItem *pasteboardItem = [NSPasteboardItem new];

    DragImageItem *dataProvider = [[DragImageItem alloc]init];

    NSData *data = [self.image TIFFRepresentation];

    dataProvider.data = data;

    [pasteboardItem setDataProvider:dataProvider forTypes:@[kPasteboardTypeName]];

    NSDraggingItem *draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteboardItem];

    [draggingItems addObject:draggingItem];

    [self beginDraggingSessionWithItems:draggingItems event:theEvent source:self.dragSourceDelegate];

}

拖放数据定义

拖放源跟拖放目标方约定数据的type类型,拖放源的数据按type类型存储,拖放接收方注册根据类型来获取数据。

NSPasteboardItem

使用NSPasteboardItem定义拖放携带的基本数据信息。NSPasteboardItem提供了3种基本的定义数据的方法和一个使用代理提供数据的方式。

1.基本的数据定义方法
可以定义NSData,NSString,id类型的数据
- (BOOL)setData:(NSData *)data forType:(NSString *)type;
- (BOOL)setString:(NSString *)string forType:(NSString *)type;
- (BOOL)setPropertyList:(id)propertyList forType:(NSString *)type;

2.使用代理方式提供数据
- (BOOL)setDataProvider:(id )dataProvider forTypes:(NSArray *)types;

实现代理类中定义获取数据的协议方法,实际上实现部分仍然是调用NSPasteboardItem的基本方法把数据存储起来。

  • (void)pasteboard:(nullable NSPasteboard *)pasteboard item:(NSPasteboardItem *)item provideDataForType:(NSString *)type;
    {

    [item setData:self.data forType:type];

    }

NSDraggingItem

使用NSDraggingItem包装NSPasteboardItem

NSDraggingItem *draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item];

拖放可视化定义

定义拖放过程中的跟随鼠标移动的图像。

draggingFrame中定义了拖放图像的位置,当有多个拖放对象一起拖放时,每个拖放图像的位置是不一样的。
imageComponentsProvider block块中定义了NSDraggingImageComponent对象, 可以在drawingHandler使用Cocoa绘图方法绘制出代表拖放的图像。block最后返回数据对象,方便表示多个不同的拖放源。

NSDraggingItem *draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteboardItem];
draggingItem.draggingFrame = NSMakeRect(0, 0, 16, 16);
draggingItem.imageComponentsProvider = {
NSDraggingImageComponent *component = [NSDraggingImageComponent draggingImageComponentWithKey:NSDraggingImageComponentIconKey];
component.frame = NSMakeRect(0, 0, 16, 16);
component.contents = [NSImage imageWithSize:NSMakeSize(16, 16) flipped:NO drawingHandler:NSRect rect {
NSImage *image = ....
[image drawInRect:rect];
return YES;
}];
return @[component];
};

拖放源协议NSDraggingSource

返回允许的拖放操作,代理必须实现的方法

  • (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context;

可选3个方法分别是拖放开始,拖放移动,拖放结束时的位置。
@optional
- (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint;
- (void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint;
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation;

拖放内容

接收拖放

拖放接收方即拖放目标对象实现拖放目标方协议,通过协议的一系列方法依次调用,最终完成跟拖放源的数据交换,结束拖放。

注册接受的拖放类型

NSView 或 NSWindow 提供了注册拖放类型的方法registerForDraggedTypes

[self.window registerForDraggedTypes:[NSArray arrayWithObjects:
NSColorPboardType, 
NSFilenamesPboardType, 
NSURLPboardType,
NSFilesPromisePboardType
nil]];

可以使用Cocoa系统中预定义的类型,也可以自定义一种类型。

拖放目标方协议NSDraggingDestination

NSView 或 NSWindow 已经继承了NSDraggingDestination协议,需要拖放目标类来实现协议方法。

@protocol NSDraggingDestination 
@optional
//拖放进入目标区
- (NSDragOperation)draggingEntered:(id )sender;
//拖放进入目标区移动
- (NSDragOperation)draggingUpdated:(id )sender; 
//拖放退出目标区,拖放的图像会弹回到拖放源
- (void)draggingExited:(id )sender;

//拖放预处理,一般是根据拖放类型type,判断是否接受拖放。
- (BOOL)prepareForDragOperation:(id )sender;

//允许接收拖放,开始接收处理拖放数据
- (BOOL)performDragOperation:(id )sender;

//拖放完成结束
- (void)concludeDragOperation:(id )sender;

。。。

@end

拖放接收方处理过程

拖放源的代表图像进入拖放目标区,触发draggingEntered方法。draggingEntered中必须返回一种有效的拖放操作,如果返回NSDragOperationNone,接收拖放,拖放的图像会弹回到拖放源。

鼠标在拖放目标区移动,触发draggingUpdated。

鼠标退出目标区,调用draggingExited方法,拖放的图像会弹回到拖放源。

拖放源代表图像完全进入拖放目标区,释放鼠标按键。触发prepareForDragOperation方法,1. 如果返回NO,拖放的图像会弹回到拖放源,接收拖放。2. 如果返回YES,调用performDragOperation处理拖放,获取拖放数据,进行相关操作。最后调用concludeDragOperation完成一次成功的拖放。

拖放编程实践

前面的章节完整的阐述了拖放处理的全流程,实际应用中很多场景,不需要进行拖放源部分的编程,只需要进行拖放目标方的编程处理。

文件拖放处理

从OSX 系统Finder目录中拖放一个文件到应用。

自定义应用内部接收拖放的view视图类FileDragView,注册拖放类型,实现目标拖放协议NSDraggingDestination。

注册拖放类型
- (void)awakeFromNib {

[super awakeFromNib];[self registerForDraggedTypes:@[NSFilenamesPboardType]];

}

拖放文件进入拖放区,返回拖放操作类型
- (NSDragOperation)draggingEntered:(id )sender {

NSLog(@"drag operation entered");NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];NSPasteboard *pboard = [sender draggingPasteboard];if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {    if (sourceDragMask & NSDragOperationLink) {        return NSDragOperationLink;    } else if (sourceDragMask & NSDragOperationCopy) {        return NSDragOperationCopy;    }}return NSDragOperationNone;

}

执行拖放处理,获取文件路径。可以通过代理或通知形式发送消息通知Controller去处理。
- (BOOL)performDragOperation:(id )sender
{
NSPasteboard *pboard = [sender draggingPasteboard];

NSLog(@"drop now");if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {    NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];    NSInteger numberOfFiles = [files count];    if(numberOfFiles>0)    {        NSString *filePath = [files objectAtIndex:0];        //if(self.delegate){           // [self.delegate didFinishDragWithFile:filePath];        //}        return YES;    }}else{    NSLog(@"pboard types(%@) not register!",[pboard types]);}return YES;

}

NSTableView的内部数据拖放处理

自定义NSTableView类DragTableView,在awakeFromNib方法中注册拖放类型为kDragTableViewTypeName.

实现NSTableViewDataSource协议,实现下面3个方法

1.数据复制到NSPasteboard
这里是将拖放时选择的的行rowIndexes 按kDragTableViewTypeName类型注册存储到系统剪贴板
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet )rowIndexes toPasteboard:(NSPasteboard)pboard {
// Copy the row numbers to the pasteboard.
NSData *zNSIndexSetData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
[pboard declareTypes:[NSArray arrayWithObject:kDragTableViewTypeName] owner:self];
[pboard setData:zNSIndexSetData forType:kDragTableViewTypeName];
return YES;
}

  1. validate,返回允许的拖放操作类型
  2. (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op {

    //Add code here to validate the drop
    //NSLog(@validate Drop);
    return NSDragOperationEvery;
    }

3.拖放数据处理。
从剪贴板获取到rowIndexes,进行相关处理。
- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id )info
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation {

NSPasteboard* pboard = [info draggingPasteboard];NSData* rowData = [pboard dataForType:kDragTableViewTypeName];NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];NSInteger dragRow = [rowIndexes firstIndex];return YES;

}

NSOutlineView的数据拖放处理

  1. 将拖放的节点数据 存储到剪切板
  2. (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard{

    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:items];

    [pboard declareTypes:[NSArray arrayWithObject:kDragOutlineViewTypeName] owner:self];

    [pboard setData:data forType:kDragOutlineViewTypeName];

    return YES;

}

2.validate,返回允许的拖放操作类型
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index {
// Add code here to validate the drop

NSLog(@"validate Drop");return NSDragOperationEvery;

}

3.从剪贴板获取到拖放的数据items,进行相关处理。
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id)info item:(id)item childIndex:(NSInteger)index {

NSPasteboard* pboard = [info draggingPasteboard];NSData* data = [pboard dataForType:kDragOutlineViewTypeName];NSArray* items = [NSKeyedUnarchiver unarchiveObjectWithData:data];....}
0 0