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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343