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
}