本了继上节内容,讲述几种常用的拖放场景示例,包括文件、表格和树的拖放实现。
文件拖放
实现从系统目录拖放文件到App中。
自定义接收视图
自定义应用内部接收拖放的view视图类FileDragView,注册拖放类型,实现目标拖放协议NSDraggingDestination。
//自定义拖放类型
public let kImagePboardTypeName = "macdev.io.fileDrag"
let NSFilenamesPboardType = NSPasteboard.PasteboardType(rawValue: kImagePboardTypeName)//文件拖放应用代理协议
protocol FileDragDelegate: class {func didFinishDrag(_ filePath:String)
}class FileDragView: NSView {weak var delegate: FileDragDelegate?override func awakeFromNib() {super.awakeFromNib()//注册文件拖放类型self.registerForDraggedTypes([NSFilenamesPboardType])}//MARK: NSDraggingDestination//开始拖放,返回拖放类型override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {let sourceDragMask = sender.draggingSourceOperationMask()let pboard = sender.draggingPasteboard()let dragTypes = pboard.types! as NSArrayif dragTypes.contains(NSFilenamesPboardType) {if sourceDragMask.contains([.link]) {return .link}if sourceDragMask.contains([.copy]) {return .copy}}return .generic}//拖放文件进入拖放区,返回拖放操作类型override func performDragOperation(_ sender: NSDraggingInfo?)-> Bool {let pboard = sender?.draggingPasteboard()let dragTypes = pboard!.types! as NSArrayif dragTypes.contains(NSFilenamesPboardType) {let files = (pboard?.propertyList(forType: NSFilenamesPboardType))! as! Array<String>let numberOfFiles = files.countif numberOfFiles > 0 {let filePath = files[0] as String//代理通知if let delegate = self.delegate {NSLog("filePath \(filePath)")delegate.didFinishDrag(filePath)}}}return true}
}
实现拖放响应处理
文件拖放响应处理
class ViewController: NSViewController {//文本框@IBOutlet var textView: NSTextView!//自定义的此controller中的view@IBOutlet var dragView: FileDragView!override func viewDidLoad() {super.viewDidLoad()//设置代理self.dragView.delegate = self}
}//实现代理方法
extension ViewController: FileDragDelegate {func didFinishDrag(_ filePath:String) {let url = NSURL(fileURLWithPath: filePath)guard let string = try? NSString.init(contentsOf: url as URL, encoding: String.Encoding.utf8.rawValue)else {return}self.textView.string = string as String}
}
表格行数据拖放
在UI中添加一个表格,下面的行是可拖动的。
初始化表格
设置NSTableColumn的identifier
按下图设置
设置NSTableView的Delegate和DataSource
拖动右侧+号到左侧UI导航栏的View Controller
行拖动实现
这里把表格初始化和行拖动混在一上进心实现,最好是单独实现一个类。
import Cocoa//定义拖放事件类型
let kTableViewDragDataTypeName = "TableViewDragDataTypeName"
let tableViewDragDataTypeName = NSPasteboard.PasteboardType(rawValue: kTableViewDragDataTypeName)class ViewController: NSViewController {@IBOutlet weak var tableView: NSTableView!var datas = [NSDictionary]()override func viewDidLoad() {super.viewDidLoad()//更新数据self.updateData()//注册行拖动类型self.tableView.registerForDraggedTypes([tableViewDragDataTypeName])}func updateData() {self.datas = [["name":"john","address":"USA"],["name":"mary","address":"China"],["name":"park","address":"Japan"],["name":"Daba","address":"Russia"],]}
}extension ViewController: NSTableViewDataSource {func numberOfRows(in tableView: NSTableView) -> Int {return self.datas.count}//数据复制到剪切板func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: rowIndexes);pboard.declareTypes([tableViewDragDataTypeName], owner: self)pboard.setData(zNSIndexSetData, forType: tableViewDragDataTypeName)return true}//返回允许拖动的操作类型func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {return .every}//拖放数据处理func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {let pboard = info.draggingPasteboard()let rowData = pboard.data(forType: tableViewDragDataTypeName)let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: rowData!) as! NSIndexSetlet dragRow = rowIndexes.firstIndexlet temp = self.datas[row]self.datas[row] = self.datas[dragRow]self.datas[dragRow] = temptableView.reloadData()return true}
}extension ViewController: NSTableViewDelegate {func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {let data = self.datas[row]//表格列的标识let key = (tableColumn?.identifier)!//单元格数据let value = data[key]//根据表格列的标识,创建单元视图let view = tableView.makeView(withIdentifier: key, owner: self)let subviews = view?.subviewsif (subviews?.count)!<=0 {return nil}let textField = subviews?[0] as! NSTextFieldif value != nil {textField.stringValue = value as! String}return view}
}
树节点数据拖放
树本质上是一种表格实现,只不过只有一列,所以其初始化方式同表格相同。
初始化
代码如下:
import Cocoa// 自定义数据拖放类型
let kDragOutlineViewTypeName = "kDragOutlineViewTypeName"
let dragOutlineViewTypeName = NSPasteboard.PasteboardType(rawValue: kDragOutlineViewTypeName)class ViewController: NSViewController {lazy var nodes: Array = {return [NSMutableDictionary]()}()//表格视图@IBOutlet weak var treeView: NSOutlineView!override func viewDidLoad() {super.viewDidLoad()self.configData()self.registerDrag()self.treeView.reloadData()self.treeView.expandItem(nil, expandChildren: false)}//注册拖放func registerDrag() {self.treeView.registerForDraggedTypes([dragOutlineViewTypeName])}//配置测试数据func configData() {let group1 = NSMutableDictionary()let group2 = NSMutableDictionary()group1["name"] = "Group1"group2["name"] = "Group2"let member11 = NSMutableDictionary()member11["name"] = "member11"let member12 = NSMutableDictionary()member12["name"] = "member12"let children1 = [ member11,member12]let member21 = NSMutableDictionary()member21["name"] = "member21"let member22 = NSMutableDictionary()member22["name"] = "member22"let member23 = NSMutableDictionary()member23["name"] = "member23"let children2 = [member21,member22,member23]group1["children"] = NSMutableArray(array: children1)group2["children"] = NSMutableArray(array: children2)self.nodes.append(group1)self.nodes.append(group2)}
}
实现协议
实现相关协议,这个协议代码写在哪里,与Viewxdpt的Controller有关。
extension ViewController:NSOutlineViewDataSource {func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {if(item == nil) {return self.nodes.count}let node = item as! NSMutableDictionaryif let children = node["children"] as? NSArray {return children.count}return 0}func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {if(item==nil) {return self.nodes[index]}let node = item as! NSMutableDictionarylet children = node["children"] as! NSArrayreturn children[index]}func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {return true}//将拖放的节点数据 存储到剪切板func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool {if items.count == 0 {return false}let data = NSKeyedArchiver.archivedData(withRootObject: items)pasteboard.declareTypes([dragOutlineViewTypeName], owner: self)pasteboard.setData(data, forType: dragOutlineViewTypeName)return true}//节点允许拖放func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {return .every}//拖放数据接收处理func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {let pboard = info.draggingPasteboard()let data = pboard.data(forType: dragOutlineViewTypeName)let items = NSKeyedUnarchiver.unarchiveObject(with: data!)guard let itemsData = items else {return false}let targetNodeItem = item as! NSMutableDictionaryvar children = targetNodeItem["children"] as? NSMutableArray//如果拖放的目标节点没有孩子,则创建一个新孩子节点if children == nil {children = NSMutableArray()targetNodeItem["children"] = children}//将数据添加到目标节点的children中children?.addObjects(from: itemsData as! [AnyObject])//重新加载数据outlineView.reloadData()return true}
}extension ViewController: NSOutlineViewDelegate {func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {let view = outlineView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self)let subviews = view?.subviewslet field = subviews?[0] as! NSTextFieldlet node = item as! NSMutableDictionaryif let name = node["name"] {field.stringValue = name as! String}return view}func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {return 20}}