Zhihao's Studio.

更优雅的文件添加方式Drag & Drop

Word count: 1,302 / Reading time: 5 min
2018/01/24 Share

前言

前面基本完成了FileHider主要功能的开发工作,开源地址,该软件在百度网盘下载次数近100次,获得了一些人的认可,但也有一些人提出了不同的意见。感谢认可这款软件价值的人,同时也需要虚心听取不同意见。

不同意见主要集中在三点:

  • FileHider实现的功能完全可以由命令行代替。关于这点,我不想否认,但是话说回来,并不是每个人都是程序员,都会使用terminal,顶级的程序员甚至可以不用GUI界面在terminal里完成所有的工作,但是有GUI界面大部分情况下还是能够提升普通人的工作效率的。况且,假设你今天隐藏了一个文件,也许你明天还记得,后天还记得,但是一个星期后呢?一个月后呢?
  • FileHider没有加密功能。关于这点,我在软件设计的初衷里已经说了,FileHider的定位是将某些文件对从你身边走过,可以看到你屏幕的人进行的隐藏,而不是可以操作你电脑的人。
  • 某个文件不想隐藏了,想将其删掉,可以将remove file换成简单的-,跟左边的+也对应。这确实是一个好的建议,在最新版本的FileHider中采纳了该建议,感谢。

关于建议部分,有个网友跟我的想法不谋而合,那就是每次添加文件都需要从文件选择器里从根路径一层一层找到想要隐藏的文件,这很麻烦;更多时候,那些文件就在Finder中,如果可以直接从Finder窗口中拖拽到FileHider中,那就太Mac Native了。于是,我就将这一功能实现了。

Drag & drop in FileHider

Drag & drop

Drag & drop in D3JS

其实我对Drag & drop并不陌生,在之前开发网页可视化组件时,就多次使用这一特性。支持这一操作,确实会增加可视化组件的可交互性。在D3JS中,Drag被分为了三个阶段,即dragstart、drag、dragend,分别对应于drag时间的开始进行中结束,类似于NSView的一个生命周期,在不同的阶段,实现不同的回调函数即可对Drag事件的全过程进行完整的控制。

Drag & drop 在其他地方的应用

在ios11中,得益于iPad Pro的大屏幕,也有了Drag & drop功能,虽然没仔细研究过,但应该是和macOS里的非常类似。

另外,锤子科技引以为傲的一步(oneStep)也实现了数据在不同应用间使用手势传递的功能。由于没有使用过,就不妄加评论了,但不难看出Drag & drop是现阶段人机交互的趋势,因为它真的很自然,借用王自如的一句话,虽然用在这里可能并不是很合适。

真正的科技,就是让你感受不到科技的存在。

Drag & drop in FileHider

FileHider只需要实现Drag & drop的一半,因为它只需要接收外部拖拽进来的文件,并获取文件路径,将文件添加到隐藏文件列表中即可。

通过研究Drag & drop的API文档发现它的设计和D3JS的设计有类似之处,都提供了对动作完整生命周期进行控制的钩子。但是似乎macOS中提供了更多的钩子,比如监控拖拽东西进来没有释放便移出去的情况(draggingExited)。

1
2
3
override func draggingExited(_ sender: NSDraggingInfo?) {
isReceivingDrag = false
}

相对应的,有刚进来时的钩子(draggingEntered)。

1
2
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
}

对于FileHider来说,我们需要指定TableView为Drag & drop事件的终点,并指定可接受的文件类型,并在drag结束后,获取文件的完整路径,添加到tableView的datasource对应的数组中。

具体实现如下:首先生成DragDestinationView类,继承自NSView子类。由于NSView天然地实现了NSDraggingDestination协议,因此直接override相应的方法即可。然后在stroyboard页面指定Drag & drop事件的终点对应的NSView为DragDestinationView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
protocol FileDragDelegate : class{
func didFinishDrag(_ filePath:String)
}
class DragDestinationView: NSView {
weak var delegate: FileDragDelegate?
override func awakeFromNib() {
super.awakeFromNib()
//注册可接受文件类型
self.register(forDraggedTypes: [NSFilenamesPboardType])
}
//文件进入NSView
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
let sourceDragMask = sender.draggingSourceOperationMask()
let pboard = sender.draggingPasteboard()
let dragTypes = pboard.types! as NSArray
if 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 NSArray
if dragTypes.contains(NSFilenamesPboardType) {
let files = (pboard?.propertyList(forType: NSFilenamesPboardType))! as! Array<String>
let numberOfFiles = files.count
if numberOfFiles > 0 {
let filePath = files[0] as String
if let delegate = self.delegate {
NSLog("filePath \(filePath)")
delegate.didFinishDrag(filePath)
}
}
}
return true
}
}

在主ViewController中生成该NSView对应的Outlet,并实现FileDragDelegate协议,实现协议中的方法,即Drag & drop事件完成后需执行的逻辑即可。

1
2
3
4
5
6
7
8
9
10
extension ViewController: FileDragDelegate {
func didFinishDrag(_ filePath:String) {
let url = NSURL(fileURLWithPath: filePath)
filesList.append(url as URL)
print(url)
tableview.reloadData()
}
}

其他Drag & drop教程

Ray家出过Drag & drop更系统、完整的介绍,这里贴出链接,供感兴趣,并想进一步学习的各位参考。

完整的Drag & drop session

CATALOG
  1. 1. 前言
  2. 2. Drag & drop
    1. 2.1. Drag & drop in D3JS
    2. 2.2. Drag & drop 在其他地方的应用
    3. 2.3. Drag & drop in FileHider
  3. 3. 其他Drag & drop教程