原文地址 翻译:DeveloperLx
自从Mac发明开始,拖拽和投放就是用户交互的一部分。典型的(quintessential)例子就是Finder,你可以拖拽文件来进行排列组织,或将它们投放到垃圾桶中。
有趣的东西不至于此。
你可以从相册拖拽你最近的日落全景到你的Message,或从Dock上的Downloads中将一个文件拖拽到邮箱中。你已经get到这个点了对么?它非常得酷,并且是macOS体验的不可分割的一部分(an integral part)。
拖拽和投放从它开始已经走过了一条很长的路,现在你已几乎可以拖拽任何东西到任何地方。尝试一下,你会高兴地惊奇于这个动作和你最喜欢的app支持的类型。
在这个macOS的拖拽和投放的教程中,你将了解到怎样添加支持到你自己的app中,这样用户就可以在你的app中获得完整的Mac的体验。
这一路,你将学到怎样去:
-
在
NSView
的子类中实现核心的拖拽及投放动作 - 接受从其它应用丢过来的数据
- 提供将要拖拽到你app中其它view的数据
- 创建定制的拖拽类型
这个项目最低需要Swift 3和Xcode 8 beta 6的环境。下载 起始的项目 ,在Xcode中打开,并build和执行它。
很多孩子喜欢玩贴纸,并使用它们制成很酷的拼图,因此你将构建一个app来实现这个体验。你可以将图片拖拽到一个表面上,然后你通过添加星星(sparkle)和独角兽(unicorn)到这个view上,来提高它的档次(kick things up a few notches)。
毕竟,怎么可能会不喜欢星星和独角兽?:]
保持你的集中注意力在目标上 - 构建拖拽和投放的支持 - 起始的项目已完成了你需要的view。全部你需要做的就是了解拖拽和投放的机制。
在项目窗口中有三个部分:
- 贴纸view:你将拖拽和投放其它东西的地方
- 你将转变成两个不同的拖拽资源的小view
来看一眼项目吧。
在这个教程中,你会编辑四个指定的文件,它们在两个位置上:
Dragging Destinations
和
Dragging Sources
:
在 Dragging Destinations 中:
- StickerBoardViewController.swift :主view controller
- DestinationView.swift :在window顶部的那个view - 它将成为你拖拽动作的容器
在 Dragging Sources 中:
- ImageSourceView.swift :在底部带有独角兽的图片的view,你要将它转为拖拽的资源
- AppActionSourceView.swift :带有 星星 label的view - 你要将它转为另一种类型的拖拽的资源
这里有一些其它的文件在Drawing和Other Stuff组下,提供了一些助手方法,它们对于这个项目app是非常重要的,但是你不需要为它们花费任何时间。如果想要了解这个事是怎样构建的话,继续探索吧!
拖拽和投放包含一个 源(source) 和一个 目的地(destination) 。
你从一个source拖拽出一个项目,它需要实现
NSDraggingSource
协议。然后投放它到一个destination中,它则必须实现
NSDraggingDestination
协议,为了确定是接受还是拒绝收到的项目。
NSPasteboard
是用来帮助交换数据的类。
整个的过程被称作 dragging session :
当你用你的鼠标拖拽一样物事的时候,例如一个文件,就会发生下列的事:
- 当你开始拖拽的时候,一个 拖拽session 就开始了。
- 选择一些数据 - 通常一个图片和URL - 来表示放置在拖拽粘贴板上的信息。
- 你将图片投放到一个destination上,他会选择拒绝还是接受它,并采取一些动作 - 例如,移动文件到另一个目录下。
- 拖拽session 结束。
这就是它的要点(gist)。这是一个相当简单的概念!
第一件事,是为了从Finder和其它app接收图片,创建一个拖拽的destination。
拖拽的destination是一个view或window,它接受来自拖拽粘贴板的数据类型。你要通过遵守(adopt)
NSDraggingDestination
协议来创建拖拽的目的地。
这个图表从拖拽destination的角度,展示了对于拖拽session的剖析。
创建destination包含以下几个步骤:
- 当构建view的时候,你必须声明从任何的拖拽session中可以接受的类型。
- 当一个拖拽的图片进入这个view的时候,你需要去实现决定这个view是否接受这种数据类型的逻辑,并让拖拽session知道这个决定。
- 当拖拽的图片“着陆”(lands on)在这个view上时,你会使用从拖拽粘贴板而来的数据,去展示它在你的view上。
是时候来撸一些代码了!
在项目的navigator中选择 DestinationView.swift 并找到方法:
func setup() { }
将其替换为:
var acceptableTypes: Set<String> { return [NSURLPboardType] } func setup() { register(forDraggedTypes: Array(acceptableTypes)) }
这个代码定义了支持类型的集合。在这个case中,仅支持
URLs
。然后,调用
register(forDraggedTypes:)
来接受包含这些类型的拖拽。
添加下列的代码到
DestinationView
中来分析拖拽session的数据:
//1. let filteringOptions = [NSPasteboardURLReadingContentsConformToTypesKey:NSImage.imageTypes()] func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool { var canAccept = false //2. let pasteBoard = draggingInfo.draggingPasteboard() //3. if pasteBoard.canReadObject(forClasses: [NSURL.self], options: filteringOptions) { canAccept = true } return canAccept }
你在这里做了几件事:
- 创建一个字典来定义期望的URL类型(图片)。
- 从拖拽session信息中获取对拖拽粘贴板的引用。
- 询问粘贴板它是否包含任何的URL,以及这些URL是指向图片的。如果有图片的话,就接受这个拖拽。否则,拒绝它。
NSDraggingInfo
是一个协议,声明了提供有关拖拽session的信息的方法。你不会创建它们,也不会在事件之间储存它们。系统会在拖拽session期间创建必要的对象。
当这个app接收到图片时,你可以使用这个信息去提供反馈给拖拽session。
NSView
遵守
NSDraggingDestination
协议,因此你需要在
DestinationView
的实现中添加下列代码去覆盖
draggingEntered(\_:)
方法:
//1. var isReceivingDrag = false { didSet { needsDisplay = true } } //2. override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { let allow = shouldAllowDrag(sender) isReceivingDrag = allow return allow ? .copy : NSDragOperation() }
上面的代码做了这些事:
-
创建了一个名为
isReceivingDrag
的property,以便去追踪当前有拖拽session在这个view中,并含有你想要的数据。每次设置时,都会触发这个view的重绘。 -
覆盖
draggingEntered(\_:)
方法,并确定它是否接受这个拖拽操作。
在第二部分,这个方法需要返回一个
NSDragOperation
。你有可能注意到鼠标指针的变化是依赖于你按住的键或拖拽的destination的。
例如,如果你在 Finder 的拖拽期间按住 Option 键,那个指针就会获得一个绿色的 + 符号,用来展示一个文件的拷贝即将发生。这个值是你如何控制那些指针的变化。
在这个配置中,如果拖拽粘贴板带有一个图片,它就返回
.copy
来向用户展示你将要复制图片。否则,如果它不接受拖拽的项目,它就返回
NSDragOperation()
。
进入view的东西同时也有可能退出,所以app需要处理当一个拖拽session没有投放,就退出了你的view时的情况。添加下列的代码:
override func draggingExited(_ sender: NSDraggingInfo?) { isReceivingDrag = false }
你已经覆盖了
draggingExited(\_:)
方法,并设置
isReceivingDrag
变量为
false
。
你几乎已经完成了第一段的代码!用户喜欢当一些事在背后发生时,能够看到一个视觉上的提示,所以,接下来,你要添加一小段绘图的代码,来保持你的用户在体验闭环(loop)上。
仍然是在
DestinationView.swift
中,找到
draw(:_)
并用下列代码替换它。
override func draw(_ dirtyRect: NSRect) { if isReceivingDrag { NSColor.selectedControlColor.set() let path = NSBezierPath(rect:bounds) path.lineWidth = Appearance.lineWidth path.stroke() } }
当一个有效的拖拽进入到这个view时,这个代码就会绘制出一个系统颜色的代码。除了看起来很尖锐,它通过当接受一个拖拽的项目时,提供视觉的表现,使你的app与系统其余部分保持一致。
注意: 想要了解更多关于自定义绘图的内容?查看我们的 macOS教程:Core Graphics 。
build并执行,然后尝试将一个图片从Finder拖拽到 StickerDrag 中。如果你没有顺手的图片,就使用项目目录中的 sample.jpg 吧。
你可以看到,当在这个view中时,指针会带有一个 + 的符号。并且view会在周围绘制一个边框。
当你退出这个view的时候,边框和 + 就会消失;只要你拖拽的不是一个图片文件,绝对任何事都不会发生。
现在,到了这个部分的最后一步:你必须接受拖拽,处理数据,并告知拖拽session这已发生。
添加下列代码到
DestinationView
类的实现中:
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool { let allow = shouldAllowDrag(sender) return allow }
当你在这个view中释放鼠标的时候,系统就会调用上面的方法;这是最后一次接受或拒绝拖拽的机会。返回
false
就会拒绝,导致拖拽的图像跑回了它起始的位置。返回
true
意味着view接受了这个image。当接受时,系统就会移除拖拽的图片并调用协议序列中的下一个方法:
performDragOperation(\_:)
。
添加下列方法到
DestinationView
中:
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool { //1. isReceivingDrag = false let pasteBoard = draggingInfo.draggingPasteboard() //2. let point = convert(draggingInfo.draggingLocation(), from: nil) //3. if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:filteringOptions) as? [URL], urls.count > 0 { delegate?.processImageURLs(urls, center: point) return true } return false }
你做的事情有:
-
重置
isReceivingDrag
标致为false
。 - 将基于window的坐标转为view的相对坐标。
-
将图片的URL移交给delegate进行处理,并返回
true
- 否则,你拒绝拖拽操作并返回false
。
注意
:感觉更好了?如果你要做一个带动画的投放序列,
performDragOperation(:_)
会是开始动画最好的地方。
祝贺!你刚刚完成了第一部分,并完成了
DestinationView
接收拖拽要做的所有工作。
接下来你将使用
DestinationView
在它的delegate中使用的数据。
打开
StickerBoardViewController.swift
并将其指定为
DestinationView
的delegate。
为了恰当地使用它,你需要实现
DestinationViewDelegate协议
的方法,将图片放到目标的层上。找到
processImage(\_:center:)
并用下列的代码替换它。
func processImage(_ image: NSImage, center: NSPoint) { //1. invitationLabel.isHidden = true //2. let constrainedSize = image.aspectFitSizeForMaxDimension(Appearance.maxStickerDimension) //3. let subview = NSImageView(frame:NSRect(x: center.x - constrainedSize.width/2, y: center.y - constrainedSize.height/2, width: constrainedSize.width, height: constrainedSize.height)) subview.image = image targetLayer.addSubview(subview) //4. let maxrotation = CGFloat(arc4random_uniform(Appearance.maxRotation)) - Appearance.rotationOffset subview.frameCenterRotation = maxrotation }
这个代码玩了下列的招(tricks):
- 将 Drag Images Here label隐藏。
- 为投放的图片,算出其保持长宽比的情况下,最大的尺寸。
- 使用这个尺寸构建了一个subview,将它的中心定位在投放点上,并将其添加到view的图层上。
- 随机地旋转这个view一点角度,让它看起来更好。
到这里,你已经准备好了去实现处理投放到这个view的图片的URL的方法。
使用下列代码替换
processImageURLs(\_:center:)
方法:
func processImageURLs(_ urls: [URL], center: NSPoint) { for (index,url) in urls.enumerated() { //1. if let image = NSImage(contentsOf:url) { var newCenter = center //2. if index > 0 { newCenter = center.addRandomNoise(Appearance.randomNoise) } //3. processImage(image, center:newCenter) } } }
你在这里做的是:
- 使用URL的内容中创建图片。
- 如果这里有超过一张的图片,就将图片的中心偏移一些,来创建分层的,随机的效果。
- 将图片和中心点传递到上一个方法,让它可以添加图片到view上。
现在build并执行,然后拖拽一个(或几个)图片到app的window上,投放它!
看看那张图板,等待大胆的幻想~~
你大约已走了一半的路,探索过怎样使如何一个view变为拖拽的destination,以及怎样使它接受一个标准类型 - 在这个case中,是图片的URL。
你已经玩转了接受这一头的,但是发送这一头的呢?
在这一部分,你将学到怎样通过让那些独角兽和星星自由地活动,并在适当的环境中给用户的图像带来快乐,让你的app充满能量(supercharge your app)。
所有的拖动source都必须遵循
NSDraggingSource
协议。这个MVP(最重要的玩家)承担了将一个或多个类型的数据(或数据的“承诺”(promise))放置到拖拽板上的任务。它还提供一个拖拽图片来展示数据。
当这个图片最终着陆在它的目标时,这个destination就从粘贴板中解档数据。或者是,拖拽source就可以实现提供数据的承诺。
你需要提供两种不同类型的数据:一个标准的 Cocoa 类型(一个image)和你创建的定制类型。
拖拽source将是
ImageSourceView
- 包含独角兽的view的类。你的目的很简单:把这个独角兽弄到你的拼图(collage)上。
这个类需要遵循必须的协议
NSDraggingSource
和
NSPasteboardItemDataProvider
,因此打开
ImageSourceView.swift
并添加下列的extension:
// MARK: - NSDraggingSource extension ImageSourceView: NSDraggingSource { //1. func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { return .generic } } // MARK: - NSDraggingSource extension ImageSourceView: NSPasteboardItemDataProvider { //2. func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) { //TODO: Return image data } }
-
这个方法是
NSDraggingSource
协议要求的。它告诉拖拽session你在尝试的操作类型,当用户从这个view中拖拽时。在这个case中,它是一个泛型的操作。 -
这实现了强制的
NSPasteboardItemDataProvider
方法。后面还有更多的东西 - 现在的只是一点点的根。
在真实世界的项目中,启动一个拖拽session的最佳时机取决于你的UI。
在这个项目app中,这个你工作所在的特定的view,是为了拖拽的单独的目标存在的,因此你将启动拖拽在
mouseDown(with:)
这里。
在其它case中,它启动在
mouseDragged(with:)
事件中启动可能会更恰当。
在
ImageSourceView
类的实现中添加下列方法:
override func mouseDown(with theEvent: NSEvent) { //1. let pasteboardItem = NSPasteboardItem() pasteboardItem.setDataProvider(self, forTypes: [kUTTypeTIFF]) //2. let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) draggingItem.setDraggingFrame(self.bounds, contents:snapshot()) //3. beginDraggingSession(with: [draggingItem], event: theEvent, source: self) }
当你点击一个view时,系统调用
mouseDown(with:)
,事件开始“滚动”(rolling)。基类的实现中没有做任何事,因此无需再去调用。在实现中的代码做了全部的事:
-
创建一个
NSPasteboardItem
,并设定这个类作为它的数据提供者。NSPasteboardItem
是一个“箱子”,可以“运载”有关被拖拽的项目的信息。NSPasteboardItemDataProvider
根据请求提供数据。在这个case中你将提供 TIFF 数据,这是在Cocoa中运载图片的标准形式。 -
创建一个
NSDraggingItem
并将粘贴板的项目(item)赋给它。存在拖拽项目(item)来提供拖拽的图片,并运载一个粘贴板项目(item),但由于它有限的寿命,你不能保留对这个项目(item)的引用。如果需要的话,拖拽session会重新创建这个对象。snapshot()
是前面提到的助手方法之一。它创建一个NSView
的NSImage
。 - 开始拖拽session。这里你触发拖拽图片,来跟随你的鼠标,直到你投放它。
build并执行。尝试拖拽独角兽到顶部的view。
这个view的一个图片跟随你的鼠标,但它滑到了你鼠标的后面,因为
DestinationView
不接受TIFF数据。
为了接受这个数据,你需要:
-
在
setup()
中更新注册的类型来接受TIFF数据 -
更新
shouldAllowDrag()
来接受TIFF类型 -
更新
performDragOperation(\_:)
来从粘贴板中拿到图片
打开 DestinationView.swift 。
替换下面这行:
var acceptableTypes: Set<String> { return [NSURLPboardType] }
为:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] } var acceptableTypes: Set<String> { return nonURLTypes.union([NSURLPboardType]) }
你刚刚注册了TIFF类型,就像你为URL做的,并创建一个子集来下次使用。
接下来,来到
shouldAllowDrag(:_)
,并添加发现
return canAccept
。输入下列代码在
return
语句之前:
else if let types = pasteBoard.types, nonURLTypes.intersection(types).count > 0 { canAccept = true }
这里你检查了
nonURLTypes
集合是否包含了任何从粘贴板接受到的类型,如果是的话,接受拖拽的操作。从你添加了一个TIFF类型到这个集合,这个view就接受从粘贴板而来的TIFF数据。
最后,更新
performDragOperation(\_:)
来解档从粘贴板来的图片数据。这相当得容易。
Cocoa想让你使用粘贴板,并提供了一个
NSImage
的带有
NSPasteboard
参数的构造方法。当你开始探索更多关于拖拽和投放的内容后,你将在
Cocoa
中发现更多的这些便利方法。
找到
performDragOperation(\_:)
,在最后添加下列的代码,就在return语句
return false
的上面:
else if let image = NSImage(pasteboard: pasteBoard) { delegate?.processImage(image, center: point) return true }
这从粘贴板中抽取一张图片,并传递给delegate来处理。
build并执行,然后拖拽独角兽到sticker view上面。
你将注意到,现在你的指针上带有了一个绿色的 + 。
destination view接受图片数据,但是当你投放的时候,图片仍然滑到了后面。啊啊啊啊啊啊啊...这里缺少了什么?
你需要获得拖拽source来提供图片数据 - 换句话说:“履行它的承诺”。
打开
ImageSourceView.swift
并用下列代码替换
pasteboard(\_:item:provideDataForType:)
的内容:
//1. if let pasteboard = pasteboard, type == String(kUTTypeTIFF), let image = NSImage(named:"unicorn") { //2. let finalImage = image.tintedImageWithColor(NSColor.randomColor()) //3. let tiffdata = finalImage.tiffRepresentation pasteboard.setData(tiffdata, forType:type) }
在这个方法中,发生了下面的事情:
-
如果期望的数据类型是
kUTTypeTIFF
,你就加载一个名为 unicorn 的图片。 - 使用提供的助手方法之一,来一任意的颜色给图片染色。毕竟,带颜色的独角兽比几个(a smattering of)全黑的独角兽更喜庆(festive)。:]
- 将图片转为TIFF数据,并将其放置到粘贴板上。
运行项目,拖拽独角兽的图片到sticker view上。它将投放带颜色的独角兽到view上。赞!
好多的独角兽!
独角兽是相当棒的(fabulous),但没有星星怎么能好?奇怪的是,没有相应于星星的 Cocoa 数据类型。我打赌你已经知道接下来要做什么了。:]
注意: 在上一部分,提供了你一个标准的数据类型。你可以在 API参考 中探索标准数据的类型。
在这一部分,你将发明你自己的数据类型。
在你的to-do列表中有下列任务:
- 用你定制的类型创建一个新的拖拽source。
- 更新拖拽destination来识别这个类型。
- 更新view controller来相应这个类型。
打开 AppActionSourceView.swift 。除了这个重要的定义,它几乎是空的:
enum SparkleDrag { static let type = "com.razeware.StickerDrag.AppAction" static let action = "make sparkles" }
这定义了你定制的拖拽类型,和动作的id。
拖拽source的types必须是 Uniform Type Identifiers 。这些是描述数据类型的反向编码的名称路径。
例如,如果你将
kUTTypeTIFF
的值打印出来,你将看到它的值就是字符串
public.tiff
。
为了避免和已存在的类型的冲突,你可以定义id像这样: bundle identifier + AppAction 。这是一个任意的值,但你可以保持它在应用的私有命名空间下,来最小化使用了已存在名称的危险。
如果你尝试用一个不是
UTI
的类型构建
NSPasteboardItem
,和这个操作将会失败。
现在你需要让
AppActionSourceView
遵循
NSDraggingSource
协议。打开
AppActionSourceView.swift
并添加下列的extension:
// MARK: - NSDraggingSource extension AppActionSourceView: NSDraggingSource { func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { switch(context) { case .outsideApplication: return NSDragOperation() case .withinApplication: return .generic } } }
这个代码块和
ImageSourceView
不同,因为你将放置私人的数据到粘贴板上,它们在app的外部是没有意义的。这就是为什么当鼠标被拖拽到你的应用之外时,你使用
context
参数来返回
NSDragOperation()
。
你早已熟悉了下一步。你需要覆盖
mouseDown(with:)
事件来用一个粘贴板项目(item)启动一个拖拽session。
添加下列的代码到
AppActionSourceView
类的实现中:
override func mouseDown(with theEvent: NSEvent) { let pasteboardItem = NSPasteboardItem() pasteboardItem.setString(SparkleDrag.action, forType: SparkleDrag.type) let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) draggingItem.setDraggingFrame(self.bounds, contents:snapshot()) beginDraggingSession(with: [draggingItem], event: theEvent, source: self) }
在这里你做了什么?
你构建了一个粘贴板项目(item),并为你定制的类型,直接将数据放到它内部。在这个case中的数据是一个接收的view可以用来做决定的定制动作的id。
你可以看到这个和
ImageSourceView
在某种程度上的不同之处。投放的数据直接到了粘贴板上,而不是将数据的产生推迟到当view使用
NSPasteboardItemDataProvider
协议接受了投放时。
为什么你要用
NSPasteboardItemDataProvider
协议?因为你希望当你在
mouseDown(with:)
中开始拖拽session时,东西能够移动得尽可能得快。
如果你移动的数据花费了太长的时间在粘贴板上构建,它会阻塞(jam up)主线程,并在用户开始拖拽时,让他们感受到一个令人沮丧的可察觉的延迟。
在这个case重,你只是放置了一个小字符创到粘贴板上,以便它可以立即执行。
下一步,你必须让destination view接受这个新的类型。现在,你早已知道了怎么可以做到。
打开
DestinationView.swift
并添加
SparkleDrag.type
到注册类型。替换下面这行:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] }
为:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF),SparkleDrag.type] }
现在SparkleDrags就可以被接受了!
performDragOperation(:_)
需要一个新的
else-if
字句,因此添加下列的代码到这个方法的最后
return false
之前:
else if let types = pasteBoard.types, types.contains(SparkleDrag.type), let action = pasteBoard.string(forType: SparkleDrag.type) { delegate?.processAction(action, center:point) return true }
这就从粘贴板中抽取了字符串。如果它符合你定制的类型,你就将动作传回给delegate。
你已几乎完成,只需要更新
StickerBoardViewController
来处理动作指令。
打开
StickerBoardViewController.swift
并替换
processAction(\_:center:)
为:
func processAction(_ action: String, center: NSPoint) { //1. if action == SparkleDrag.action { invitationLabel.isHidden = true //2. if let image = NSImage(named:"star") { //3. for _ in 1..<Appearance.numStars { //A. let maxSize:CGFloat = Appearance.maxStarSize let sizeChange = CGFloat(arc4random_uniform(Appearance.randonStarSizeChange)) let finalSize = maxSize - sizeChange let newCenter = center.addRandomNoise(Appearance.randomNoiseStar) //B. let imageFrame = NSRect(x: newCenter.x, y: newCenter.y, width: finalSize , height: finalSize) let imageView = NSImageView(frame:imageFrame) //C. let newImage = image.tintedImageWithColor(NSColor.randomColor()) //D. imageView.image = newImage targetLayer.addSubview(imageView) } } } }
以上代码做了下列的事:
- 仅响应已知的动作
- 从bundle中载入一个星的图片
- 制作一些这个星的图片的拷贝,并...
- 生成一些随机的数字来改变星的位置。
-
创建一个
NSImageView
并设置它的frame。 - 这个图片设定一个随机的颜色 - 除非你要向David Bowie致敬(tribute),黑色的星总是有些野蛮。 (译者注:David Bowie是英国著名的摇滚音乐家,2016年去世,《Blackstar》是其遗作,在59届格莱美奖中获五项大奖)
- 放置图片到这个view上。
运行项目。现在你可以拖拽星星的view到sticker view上了,来添加“一阵”(a spray of)星星到你的view上。
恭喜,你已经在你自己的app中创建了一个定制的拖放UI!
你可以使用 Save Image To Desktop 按钮来保存你的图片作为一个 JPG ,名叫 StickerDrag 。或者更进一步,将它发送给 @rwenderlich 的团队。
这里是完整项目的 源码 。
这个macOS的拖放教程,覆盖了Cocoa拖放机制的基础,包括:
- 创建一个拖拽destination并接受几种不同类型的数据
- 使用拖拽session的生命循环,来提供给用户拖拽操作的反馈
- 从粘贴板中解码信息
- 创建一个拖拽source,并提供推迟的数据
- 创建一个提供指定数据类型的拖拽source
现在你已经有了用来在任意mac app中支持拖放的知识和经验了。
这里有更多要去学的。
你可以学习诸如如何在拖拽期间改变拖拽的图片,或实现一个动画的投放过渡效果,或使用承诺的文件 - Photos是一个应用程序,可将承诺的数据放在拖拽粘贴板上。
另一个有趣的话题是怎么在
NSTableView
和
NSOutlineView
中使用拖放,它们的工作方式略有不同。可以在以下的资源中进行了解:
- 苹果的 拖拽编程话题
- 苹果的 沙盒和安全范围内的数据 ,在这里你将找到,有关如果应用在沙盒中,如何让拖拽生效。
- 示例代码(用OC写的): CocoaDragAndDrop
- 示例代码(用OC写的): DragNDropOutlineView
如果你有任何关于这个macOS的拖放教程的问题或意见,请加入下面的讨论!记住,有时候生活是拖拽的体验,但每件事遇到独角兽和星星都会变得更好。:]