Drag-Drop
Drag/Drop拖拽操作已经成为OS X用户界面的一部分.例如你可以从Finder中将文件拖入垃圾桶.可以从“照片”中拖动一张图片,然后将其放入“备忘录”中,或者从“下载”里拉出一个文件,然后直接放入电子邮件。
Drag/Drop(拖拽)提供了在应用与OS X系统,不同应用之间,应用内部多种场景下资源,文件,数据可视化交换的一种用户体验。
可以拖拽的视图(view)或窗口(window)称为拖放源(
Drag Sources
),接收拖放的视图或窗口称为拖放目标(Drag Destination
)。
基本流程
用户鼠标点击拖拽拖放源
Drag Source
,发起一次拖放操作。拖放源开始准备,将拖放数据打包成Dragging ltem
对象,缓存入系统剪贴板Pasteboard
.代表拖放源
Drag Source
的图标顺着鼠标拖放的轨迹运动,直到鼠标进入到拖放目标区域,拖放目标Drag Destination
接收了拖放请求,拖放目标从剪贴板获取到拖放传递的数据,就完成了一次成功的拖放。如果拖放目标不能响应这个拖放请求,或者用户取消了拖放,代表拖放源Drag Sources
的图标会以动画形式弹回到拖放源开始的位置。-
拖放源和拖放目标之间是通过
系统的剪贴板缓存数据
来完成的数据交换,整个拖放过程中,涉及到拖放源和拖放目标之间一系列的交互处理流程。
Drag Source
拖拽源需要完成2个主要的工作,定义拖放数据,设置拖放的可视化图像.
-
1. 定义拖放数据
拖放的底层数据是NSData
类型,任何对象都可以转换成NSData类型。
NSPasteboardItem
中以Key/Value形式存储数据
,Data数据需要对应一种Pboard Type
,可以使用系统预先定义
的剪贴板类型,也可以自定义
一种类型。
拖放源端跟拖放目标方需要约定采用统一的Pboard type
类型,拖放源的数据按type类型存储
, 拖放接收方注册根据Pboard type
类型来获取
数据。
NSPasteboardItem
做为NSDraggingltem
内部变量,最终发起拖放时做为拖放数据传递到剪贴板
中。
使用NSPasteboardItem
定义拖放携带的数据,NSPasteboardItem
提供了三种基本的定义数据
的方法和一个使用代理
提供数据的方式。
- (1) 三种基本的数据定义方法可以定义NSData,NSString.id类型的数据
open func setData(_ data: Data, forType type: NSPasteboard.PasteboardType) -> Bool
open func setString(_ string: String, forType type: NSPasteboard.PasteboardType) -> Bool
open func setPropertyList(_ propertyList: Any, forType type: NSPasteboard.PasteboardType) -> Bool
- (2) 使用代理方式提供数据
open func setDataProvider(_ dataProvider: NSPasteboardItemDataProvider, forTypes types: [NSPasteboard.PasteboardType]) -> Bool
实现代理类中定义获取数据的协议方法,实际上实现部分仍然是调用NSPasteboardItem
的基本方法把数据存储起来。使用代理方式定义数据获取方法的好处是不需要提前初始化将Data数据存储到NSPasteboardltem
中,而只需要在目标方接收数据的时候才触发获取的代理方法。
extension DragSourceView: NSPasteboardItemDataProvider {
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType) {
let data = self.image?.tiffRepresentation
item.setData(data!, forType: type)
}
}
-
2. 设置拖放的可视化图像
NSPasteboardItem
中不仅携带了拖放数据,同时定义了拖放过程中展示的图象,即拖放过程中的跟随鼠标移动的图像。
draggingFrame
中定义了拖放图像的位置,当有多个拖放对象一起拖放时, 每个拖放图像的位置是不一样的。imageComponentsProvider block
块中定义了NSDragginglmageComponent
对象,可以在drawingHandler
使用Cocoa绘图方法绘制出代表拖放的图像。block最后返回数据对象
,方便表示多个不同的拖放源。
//拖放可视化图象设置
draggingItem.imageComponentsProvider = {
let component = NSDraggingImageComponent(key: NSDraggingItem.ImageComponentKey.icon)
component.frame = NSRect(x: 0, y: 0, width: 16, height: 16)
component.contents = NSImage.init(size: NSSize(width: 32,height: 32), flipped: false, drawingHandler: {
[unowned self] (rect) -> Bool in
self.image?.draw(in: rect)
return true
})
return [component]
}
拖拽源事件
用户鼠标点击拖拽视图对象触发MouseDown
事件,调用beginDraggingSessionWithItems
方法开始建立一个拖放的session
,开始启动拖放过程。
beginDraggingSessionWithItems
需要3个参数,按顺序依次为拖放数据items
,鼠标的NSEvent
,拖放源代理
。
override func mouseDown(with event: NSEvent) {
//拖放数据定义
let pasteboardItem = NSPasteboardItem()
//设置数据的Provider
pasteboardItem.setDataProvider(self, forTypes: [ imagePboardType])
//拖放item
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
//开始启动拖放sesson
self.beginDraggingSession(with: [draggingItem], event: event, source: self.dragSourceDelegate!)
}
- 拖拽源协议 NSDraggingSource
extension ViewController: NSDraggingSource {
//返回拖放操作类型,必须实现
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return .generic
}
//开始拖放代理回调
func draggingSession(_ session: NSDraggingSession, willBeginAt screenPoint: NSPoint) {
print("draggingSession beginAt \(screenPoint)")
}
//拖放鼠标移动时的代理回调
func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
print("draggingSession movedTo \(screenPoint)")
}
//结束拖放代理回调
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
print("draggingSession endedAt \(screenPoint)")
}
}
Drag Destination
拖放接收方即拖放目标对象
必须注册自己接收的PboardType拖放类型
。
拖放鼠标移动到拖放接收方位置区域时,系统检查拖放携带的数据类型是否跟拖放目标注册的PboardType
拖放类型一致。如果相同的话,拖放接收方则进行拖放接收处理过程。
拖放接收方通过拖放接收协议
的一系列方法依次调用,最终完成跟拖放源的数据交换结束拖放。
-
1.注册接受的拖放类型
NSView或NSWindow提供了注册拖放类型的方法registerForDraggedTypes
//拖拽进入目标区
optional func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation
//拖拽进入目标区移动
optional func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation
//拖拽退出目标区
optional func draggingExited(_ sender: NSDraggingInfo?)
//拖拽预处理,一般根据type判断是否接收
optional func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool
//开始处理拖拽数据
optional func performDragOperation(_ sender: NSDraggingInfo) -> Bool
//拖拽完成结束
optional func concludeDragOperation(_ sender: NSDraggingInfo?)
-
2.拖拽数据处理
在performDragOperation
代理方法中,从NSPasteboard
取出数据.
override func performDragOperation(_ sender: NSDraggingInfo?)-> Bool {
let pboard = sender?.draggingPasteboard()
//获取拖放数据
let items = pboard?.readObjects(forClasses: [DragImageDataItem.self], options: nil)
if (items?.count)! > 0 {
let imageDataItem = items?[0] as! DragImageDataItem
let img = NSImage(data: imageDataItem.data! as Data)
//创建绘制的图像模型类
let drawImageItem = DrawImageItem()
drawImageItem.image = img
let point = self.convert((sender?.draggingLocation())!, from: nil)
drawImageItem.location = point
//存储图象模型
self.imageItems.append(drawImageItem)
//触发视图重绘
self.needsDisplay = true
}
return true
}