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
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。