OS X 中的拖拽操作

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
}

你可能感兴趣的:(OS X 中的拖拽操作)