
在
iOS 11
和
iOS 12
上运行的
Drag & Drop
机制是一种在单个应用程序内以及不同应用程序之间以图形方式异步复制或移动数据的方法。 尽管这项技术已有30多年的历史了,但实际上它已成为
iOS
上的“突破性”技术,因为在
iOS
拖动某些内容时,
multitouch
允许您与系统的其余部分自由交互并收集数据以从不同的应用程序重置。
iOS
可以一次捕获多个元素。 此外,它们不必处于方便的选择范围内:您可以拿第一个对象,然后转到另一个应用程序并抓取其他东西-所有对象将被收集在手指下的“桩”中。 然后在屏幕上调用通用扩展坞,打开那里的任何应用程序并捕获第三个对象,然后在运行应用程序的情况下进入屏幕,并且在不释放对象的情况下将它们转储到一个打开的程序中。 这样的行动自由在
iPad
上,
iPhone
上是可能的,
iOS
Drag & Drop
范围仅限于一个应用程序的框架。
最流行的应用程序(
Safary
,
Chrome
,
IbisPaint X
,
Mail
,
Photos
,
Files
等)已经具有
Drag & Drop
机制。 除此之外,
Apple
还为开发人员提供了一个非常简单直观的
API
用于在您的应用程序中嵌入
Drag & Drop
机制。
Drag & Drop
机制就像手势一样,可以在
UIView上使用并使用
Interactions的概念(有点类似于手势),因此您可以
Drag & Drop
机制简单地视为一个真正强大的手势。
它以及手势都非常容易集成到您的应用程序中。 尤其是在您的应用程序使用
UITableView表或
UICollectionView集合的情况下,因为对它们而言
API Drag & Drop
改进并提升到了更高的抽象水平,即
Collection View
集合本身可以帮助您使用要拖放的collection元素的
indexPath Drag
。 她知道您的手指在哪里,并将其解释为您当前“拖动”
Drag
的集合元素的
indexPath ,或解释为您“拖放”
放置元素的collection元素的
indexPath 。 因此,
Collection View
集合为您提供了
indexPath ,但除此之外,它
API Drag & Drop
常规
UIView的 API Drag & Drop
绝对相同。
iOS
上的
Drag & Drop
过程分为四个不同的阶段:
升降机
抬起 (抬起)-这是用户执行
长按手势时的状态,指示要“拖放”的元素。 此时,所指示元素的非常轻量级的所谓“
lift preview
”形成了,然后用户开始拖动其手指。

拖动(拖放)
拖动 (拖放)-这是用户在屏幕表面上移动对象的时候。 在此阶段,可以修改该对象的“
lift preview
”(绿色“ +”加号或其他符号出现)...

...还允许与系统进行某些交互:您可以单击其他对象并将其添加到当前的“拖放”会话中:

掉落
用户举起手指时会
掉落 。 此时,可能会发生两件事:要么将
Drag
对象销毁,要么将
Drop
对象“拖放”到目标位置。

资料传输
如果没有取消“拖放”
拖放过程,并且发生了“
拖放 ”重置,则将发生
数据传输 (数据传输),此时“拖放点”从“源”请求数据,并进行异步数据传输。
在本教程中,我们将向您展示如何使用Image Gallery演示应用程序轻松地
Drag & Drop
机制
集成到
iOS
应用程序中,该应用程序取自
斯坦福大学作业课程CS193P 。
我们将使
Collection View
能够在外部填充图像,并使用
Drag & Drop
机制重新组织INSIDE元素。 另外,此机制将用于将
Collection View
不必要元素转储到“垃圾箱”中,该垃圾箱是常规的
UIView ,由导航面板上的按钮表示。 我们还可以使用
Drag & Drop
机制与其他应用程序共享在Gallery中收集的图像,例如
Notes
或
Notes
或
Mail
或照片库(
Photo
)。
但是在着重于“ Image Gallery”演示应用程序中
Drag & Drop
机制的实现之前,我将非常简要地介绍其主要组件。
“图片库”演示应用程序的功能
图像库应用程序的用户界面(
UI
)非常简单。 这是插入到
Navigation Controller
Image Gallery Collection View Controller
的“屏幕片段”:

该应用程序的中心部分肯定是
Image Gallery Collection View Controller
,它由
ImageGalleryCollectionViewController类支持,并且Image Gallery模型作为变量
var imageGallery = ImageGallery() :

该模型由包含图像
图像数组的
struct ImageGallery结构表示,其中每个图像由包含图像位置
url的URL (我们将不存储图像本身)及其宽高比的
struct ImageModel结构描述。

我们的
ImageGalleryCollectionViewController实现了
DataSource协议:

单元格集合中的自定义单元格包含一个
imageView图像
:UIImageView! 和
微调框活动指示器
:UIActivityIndicatorView! 并由
UICollectionViewCell类的自定义
subclass
ImageCollectionViewCell支持:
ImageCollectionViewCell类
Public API
是
imageURL图像
URL 。 一旦安装它,我们的
UI
更新,即,在该
imageURL上异步选择图像的数据并显示在单元格中。 从网络中检索数据时,
微调器活动指示器正在工作,表明我们正在检索数据。
我使用带有
qos服务质量参数的全局队列
(qos:.userInitiated)来获取给定
URL
数据,该
URL
设置为
.userInitiated,因为我是根据用户的请求选择数据的:

每次在闭包中使用自己的变量时(在我们的示例中为
imageView和
imageURL) ,编译器都会强制您将
self置于它们的前面
。 这样您就会问自己:“是否存在“内存循环链接”?” 这里我们没有明确的“
memory cycle
”,因为
自我本身没有指向此闭包的指针。
但是,在多线程的情况下,应该记住,由于
dequeueReusableCell方法,
Collection View
中的
单元是可重用的。 每次屏幕上出现一个单元(新的或重复使用的单元)时,都会从网络异步下载图像(此时,
旋转器活动指示器的“
旋转器 ”正在旋转)。
下载完成并接收到图像后,将立即更新此收集单元的
UI
。 但是,我们不等待图像加载,而是继续滚动浏览集合,标记为我们的集合单元会在不更新
UI
情况下离开屏幕。 但是,新图像应显示在下方,离开屏幕的同一单元格将被重用,但对于另一张图像,该图像可能会快速加载和更新
UI
。 此时,先前在该单元格中开始的图像加载将返回,并且屏幕将更新,这将导致错误的结果。 这是因为我们在不同的线程中运行与网络一起工作的不同事物。 他们在不同的时间回来。
我们如何解决这种情况?
在我们使用的
GCD机制的框架内,我们无法取消对离开屏幕的单元格图像的下载,但是当我们从网络获取
imageData数据时,我们可以检查导致加载此数据的
URL
url ,并将其与用户希望拥有的
URL
进行比较。此单元格,即
imageURL 。 如果它们不匹配,那么我们将不会更新
UI
单元格并等待我们需要的图像数据:

这行看起来很荒谬的代码
url == self.imageURL使所有内容在需要非标准想象力的多线程环境中都能正常工作。 事实是,多线程编程中的某些事情以与编写代码不同的顺序发生。
如果无法选择图像数据,则会生成带有错误消息的图像,该错误消息的形式为字符串“ Error”和带有“ frown”的表情符号。
Collection View
的空白区域可能会使用户感到困惑:

我们不希望带有错误消息的图像重复此错误图像的
aspectRatio ,因为在这种情况下,文本和表情符号将被拉伸或压缩。 我们希望它是中性的-正方形,即它将具有接近1.0的纵横比。

我们必须将此愿望告知我们的
Controller
,以便他纠正其
imageGallery模型中相应
indexPath的 AspectRatio纵横比。 这是一个有趣的任务,有很多解决方法,我们将选择最简单的方法-使用
可选的闭包
var closAspectRatio:(()-> Void)? 。 它可以等于
nil ,如果不需
要这样做 ,则不需要安装它:

当调用闭包
changeAspectRatio?()时,如果获取错误的数据,则使用
Optional链。 现在,对接收错误图像时对某种设置感兴趣的任何人都可以将此关闭设置为特定的设置。 这正是我们在
Controller
中的
cellForItemAt方法中执行的操作:

详细信息可以在
这里找到。
要显示具有正确的
AspectRatio的图像,请
使用 UICollectionViewDelegateFlowLayout委托的
sizeForItemAt方法:

除了
Collection View
图像
Collection View
,在我们的
UI
我们还在导航面板上放置了一个
Bar Button
,并带有一个包含“垃圾箱”作为
子视图的自定义
GarbageView图像:

在此图中,为
GarbageView本身和带有“垃圾箱”图像(实际上是透明背景)的
UIButton按钮的背景色进行了特殊更改,以便您看到将Gallery图像“转储”到“垃圾箱”中的用户。当“掉落”
掉落时,回旋余地远不只是垃圾桶图标。
GarbageView类
具有两个初始化程序,并且都使用
setup()方法:

在
setup()方法中,我还将
myButton作为
子视图添加到“垃圾桶”的图像中,该图像取自
Bar Button
的标准
Bar Button
:

我为
GarbageView设置了透明背景:

垃圾箱的大小及其位置将在
UIView类的
layoutSubviews()方法中确定,具体取决于给定
UIView的
范围 :

这是“图库”演示应用程序的初始版本,位于
Github
的
ImageGallery_beginning
文件夹中。 如果您运行此版本的“图库”应用程序,您将看到该应用程序处理测试数据的结果,我们随后将删除这些数据并专门在“外部”填写“图库”:

在我们的应用程序中实现
Drag & Drop
机制的计划如下:
- 首先,我们将使“
Collection View
“拖动” UIImage图像Collection View
能力,无论是在内部还是外部, - 然后我们将教我们的“
Collection View
图像集合接受从UIImage外部或本地进行“拖动” Drag
, - 我们还将通过垃圾桶按钮教我们的GarbageView ,以接受从本地“
Collection View
夹 Collection View
拖动的UIImage图像并将其从“ Collection View
夹 Collection View
删除
如果转到本教程的最后并完成所有必要的代码更改,您将收到“ Image Gallery”演示应用程序的最终版本,其中已实现了
Drag & Drop
机制。 它位于
Github
上的
ImageGallery_finished
文件夹中。
Collection View
Drag & Drop
机制的性能由两个新的委托提供。
第
一个委托的方法
dragDelegate被配置为初始化和自定义拖放
Drags
。
第二个委托
dropDelegate的方法完成
Drags的拖放,并且基本上在重置
Drop
以及其他类似内容时提供数据
Data transfer
和自定义动画设置。
重要的是要注意,这两个协议都是完全独立的。 如果只需要“拖动”
Drag
或仅“拖放”
Drop
,则可以使用一个或另一个协议,但是可以同时使用这两个协议,同时
Drag
“拖放”和“放置”
Drop
,这会打开其他功能
Drag & Drop
机制可以在“
Collection View
对项目进行重新排序。
从Collection View
Drag
元素
实施
Drag
协议非常简单,首先应该将
self设置为
dragDelegate委托:

而且,当然,在
ImageGalleryCollectionViewController类的顶部
,您应该说“是”,我们实现了
UICollectionViewDragDelegate协议:

一旦执行此操作,编译器便开始“抱怨”,单击红色圆圈,并询问:“是否要添加
UICollectionViewDragDelegate协议的必需方法?”
我回答:“我当然要!” 然后单击“
Fix
按钮:
UICollectionViewDragDelegate协议唯一需要的方法是
itemsForBeginning方法,它将告诉
Drag
系统我们“拖放”。 当用户开始“拖动”(
Dragging
)集合
单元格中的
单元格时,将调用
itemsForBeginning方法。
请注意,在此方法中,
Collection View
集合添加了
indexPath 。 这将告诉我们集合中的哪个元素,哪个
indexPath ,我们将“拖放”。 对于我们来说,这确实非常方便,因为它是负责使用
session和
indexPath参数
确定如何处理
Drag
。
如果返回“
draggable ”元素的数组
[UIDragItems], Drag
初始化
Drag
的“拖动”;如果返回空数组
[] ,
Drag
忽略
Drag
的“拖动”。
我将使用
indexPath参数创建一个小的
私有 dragItems函数(位于:indexPath) 。 它返回我们
需要的数组
[UIDragItem] 。

拖放式
UIDragItem是什么样的 ?
他只有一件非常重要的东西叫做
itemProvider 。
itemProvider只是可以提供将被拖动的数据的东西。
您有权问:“对根本没有数据的
UIDragItem元素进行“拖放”怎么样?” 例如,您要拖动的项目可能没有数据,因为创建此数据是一项昂贵的操作。 这可能是图像
图像或需要从Internet下载数据的东西。 很棒的事情是
Drag & Drop
操作是完全异步的。 当您开始拖放
Drag
,它实际上是一个非常轻巧的对象(
lift preview
),将其拖动到任何地方,在此“拖动”过程中什么也没有发生。 但是,一旦您将对象“拖放”到某个地方,然后成为
itemProvider ,它实际上必须为“拖放”和“抛出”的对象提供真实数据,即使它需要一定的时间。
幸运的是,有许多内置的
itemProviders 。 这些是
iOS
中已经存在的类,它们是
itemPoviders ,例如
NSString
,它允许您拖放没有字体的文本。 当然,这是一个
UIImage图像。 您可以选择并拖放
UIImages图像。
NSURL类,这绝对是很棒的。 您可以转到网页,选择
URL
然后将其“拖放”到所需位置。 这可以是文章的链接,也可以是图像的
URL
,就像在我们的演示中一样。 这些是地址簿中的
UIColor ,
MKMapItem地图
元素 ,
CNContact联系人的颜色类,您可以选择并拖动很多东西。 它们都是
itemProviders 。
我们将“拖放”
UIImage图像。 它位于带有
indexPath的“
Collection View
单元格中,它可以帮助我选择该单元
格 ,从中获取
Outlet
imageView并获取其
图像 。
让我们用几行代码来表达这个想法。
首先,我向
Collection View
请求有关与此
indexPath对应的
item元素的
单元格的
信息 。

Collection View
的
cellForItem(位于IndexPath)方法仅适用于可见的单元格,但是,在我们的情况下当然可以使用,因为我在屏幕上“拖放”了
Drag
集合元素,并且它是可见的。
因此,我得到了一个“可拖动”的
细胞 。
接下来,我使用
as运算符
? 到此单元格,以便它具有我的自定义
subclass
的TYPE。 如果这可行,那么我将获得一个
Outlet
imageView ,从中获取其图像
image 。 我只是“捕获”了该
indexPath的图像。
现在,我有了一个图像
图像 ,我所要做的就是使用结果图像
图像作为
itemProvider创建这些
UIDragItems之一,
即为我们提供数据的东西。
我可以使用
UIDragItem构造函数创建
dragItem ,该构造函数将
itemProvider作为参数:

然后,我们也使用
NSItemProvider构造函数为图像创建一个
itemProvider 。
NSItemProvider有多个构造
函数 ,但是其中有一个非常
不错的构造函数
-NSItemProvider(对象:NSItemProviderWriting) :

您只需将对象
对象提供给此
NSItemProvider构造函数,它就知道如何使
itemProvider成为对象。 作为这样的
对象,我给图像提供了从单元
格接收到的
图像 ,并获得了
UIImage的 itemProvider 。
仅此而已。 我们创建了
dragItem ,并将其作为具有一个元素的数组返回。
但在此之前我回dragItem,我打算做一两件事,即设置变量localObject为dragItem,等于产生的画面图像。
这是什么意思?如果Drag
在本地(即在应用程序内部)执行“拖放” ,则无需通过异步数据检索来遍历与itemProvider相关联的所有代码。您不需要这样做,只需要使用localObject并使用它即可。这是一种带有局部“拖放”的“短路” Drag
。我们编写的代码在拖放时将起作用Drag
集合之外的Collection View
其他应用程序,但是如果Drag
在本地拖放,则可以使用localObject。接下来,我返回一个包含dragItem元素的数组。顺便说一句,如果由于某种原因我无法获得该单元格的图像,则返回一个空数组[],这意味着取消了“拖放” 操作。除了本地对象localObject,你能记住的地方背景localContext我们的会话的会话。在我们的例子中,它将是一个collectionView 集合Drag

Drag
它是对我们有用算账:
具有“拖放” Drag
,你可以增加更多项目的项目这个“拖放”,只是做姿态水龙头上他们。因此,您可以Drag
一次拖放许多项目。而且,这很容易通过另一个委托方法UICollectionViewDragDelegate来实现,该方法与itemsForestegingning方法非常相似,该方法名为itemsForAddingTo。itemsForAddingTo方法看起来与itemsForeginning方法完全相同,并返回完全相同的东西,因为它还为我们提供了indexPath用户在“拖放”过程中“轻拍”的内容Drag
,我只需要从用户“轻拍”的单元格中获取图像,然后将其返回即可。从itemsForAddingTo方法
返回一个空数组[]会导致按常规方式解释点击手势,即选择此单元格cell。这就是拖放所需要的。我们启动该应用程序。我选择“ Venice”图像,将其按住一会儿然后开始移动…… 我们真的可以将此图像拖到应用程序中Drag

Photos
,因为您在“可拖动”图像的左上角看到绿色加号“ +”。我可以进行手势自来水从收集另一个图像“Artika”上Collection View
...
...现在我们可以将两个图像扔到应用Photos
:
由于应用程序Photos
已经内置的机制Drag & Drop
,一切正常,它的凉爽。因此,将Gallery图像“拖动” Drag
和“转储” Drop
到其他应用程序对我来说是有效的,除了将图像图像作为数组[UIDragItem]交付外,我在我的应用程序中不必做太多事情。这是该机制的许多重要功能之一。Drag & Drop
-使其在两个方向上均可轻松实现。将Drop
图像重置为收藏Collection View
现在,我们需要Drop
为我的收藏集Collection View
做一部分,以便我们可以“倾销” Drop
该收藏集内的所有“拖曳”图像。“可拖动”图像可以从外部“出现”,也可以直接在此集合内部“出现”。要做到这一点,我们做的做委托同样的事情dragDelegate,即让自己,自我,委托dropDelegate在方法的viewDidLoad中:
再一次,我们要攀登到我们班的顶部ImageGalleryCollectionViewController和验证协议实现UICollectionViewDropDelegate:
一旦添加了新协议,编译器便再次开始“抱怨”我们没有实现该协议。我们单击按钮Fix
,该协议所需的方法出现在我们面前。在这种情况下,我们被告知必须执行performDrop方法:
必须执行此操作,否则将不会发生“重置” Drop
。实际上,我将最后实现performDrop方法,因为Apple
对于该Drop
部件,还需要实现另外两个强烈推荐的方法。这是canHandle和dropSessionDidUpdate:
如果我们实现这两种方法,那么当我们将图像从OUTSIDE拖动到集合中时ollection View
,我们会得到一个小的绿色加号“ +” ,此外,它们不会尝试转储我们不了解的内容。您要使用的版本的canHandle方法,该方法用于收集ollection View
,但是该方法ollection View
看起来与常规UIView的相似方法相同,那里没有indexPath,我们只需要返回session.canLoadObjects(ofClass:UIImage.self),这意味着我接受这个分类的对象的“重置” PAS在我的收藏ollection View
:
但这还不足以将Drop
图像“转储” 到我的收藏中Collection View
。如果在Drop
集合内部发生图像的“转储”,则Collection View
当用户使用该机制重新组织自己的项目时Drag & Drop
,仅一个UIImage图像就足够了,并且canHandle方法的实现将类似于上面。但是,如果“复位” Drop
的图像来自外部的,我们必须处理只有“拖放” Drag
,这表示图像的UIImage与URL
这个形象,所以我们不打算只保留图像本身的UIImage在模型中。在这种情况下,我会回到真正的方法canHandle只有当两个条件下进行的一对夫妇&& session.canLoadObjects(ofClass:UIImage.self):session.canLoadObjects(NSURL.self ofClass) :
我已经离开,以确定我是否涉及“复位”外面或里面。我将使用计算的常量isSelf进行此操作,为此,我可以在Drop
会话会话中使用诸如本地Drag
会话localDragSession这样的东西。该本地Drag
会话又具有本地上下文localContext。如果您还记得,我们会在方法中设置此本地上下文itemsForVeginning Drag
委托 UICollectionViewDragDelegate:
我要去游览当地的情况 localContext在我的收藏,以平等的CollectionView。是的, localContext的 TYPE将是 Any,我需要使用 as运算符对TYPE Any进行强制转换? UICollectionView:
如果本地上下文(session.localDragSession?.LocalContext as?UICollectionView)等于我的collectionView集合,则计算出的变量 isSelf为 true在我的收藏夹中有一个本地“重置”按钮。如果违反了这种相等性,那么我们将在Drop
外部进行“重置” 。canHandle方法报告我们只能将这种“拖放”处理到我们的集合中。否则,进一步讲“倾销”是没有意义的。如果我们继续以“复位” ,它仍然是最多的时刻,用户都会让你的手指离开屏幕会有一个真正的“复位” ,我们必须报告使用该方法dropSessionDidUpdate委托UICollectionViewDropDelegate我们的报价UIDropProposal实现复位。Drag
Collection View
Drop
Drop
Drop
iOS
Drop
在此方法中,我们必须返回一个Drop
句子,该句子的operation参数可以具有.copy或.move或.cancel或.forbidden值。这些都是我们在处理常规UIView时通常所具有的所有可能性。但收集更进一步,并提供返回专门优惠UICollectionViewDropProposal,这是一种类UIDropProposal,并允许除手术操作指定的附加参数意图的收集。参量Collection View
subclass
Collection View
意向报告收集Collection View
的,我们是否要放置在现有的细胞内“一次性”细胞的细胞,我们要添加一个新的电池单元 .Vidite区别?就集合而言,Collection View
我们必须传达我们的意图。在我们的例子中,我们总是要添加一个新的单元格,因此您将看到我们的 intent参数等于。我们为 UICollectionViewDropProposal选择第二个构造函数:
在我们的例子中,我们总是要添加一个新的单元格,并且 intent参数将使用值 .insertAtDestinationIndexPath作为对.insertIntoDestinationIndexPath。
我再次使用计算出的常数 isSelf,如果它是一个自我重组,那么我将移动 .move,否则将复制 .copy。在这两种情况下,我们都使用 .insertAtDestinationIndexPath,即插入新的单元格。到目前为止,我还没有实现 performDrop方法,但让我们看一下集合可以Collection View
利用我们提供给它的这小部分信息来做什么。我从Safari
搜索引擎中拖动图片Google
,并且该图片的顶部会出现一个绿色的“ +”号,表示我们的图片库不仅可以接受和复制此图片URL
,还可以在图片集中提供一个位置Collection View
:
我可以在Safari
和中单击几张图片已经有3张“拖曳”的图像:
但是,如果我放开手指并“放下” Drop
这些图像,它们将不会被放置在我们的Gallery中,而只是返回到以前的位置,因为我们尚未实现performDrop方法。
您可能会看到该收藏集Collection View
已经知道我想要做什么。对于该机制而言,收集Collection View
绝对是一件绝妙的事情。Drag & Drop
,她为此具有非常强大的功能。通过编写4行代码,我们几乎没有碰过她,而她在“重置”的认识上已经走了很远Drop
。让我们回到代码并实现performDrop方法。
在这种方法中,我们将无法使用4行代码,因为performDrop方法稍微复杂一点,但不会太多。当一个“复位” Drop
,则该方法performDrop我们需要更新我们的模型,这是图像的画廊imageGallery组合图像的图像,我们需要更新我们的视觉收集的CollectionView。我们有两种不同的“重置”方案Drop
。如果Drop
我的collectionView 集合中有一个“重置” ,那么我必须Drop
在一个新位置“重置” 集合元素并将其从旧位置删除,因为在这种情况下,我将移动(.move)这个集合元素。这是一项琐碎的任务。来自另一个应用程序的“重置” Drop
,则我们必须使用“已拖动” item元素的itemProvider属性来选择数据。当我们在collectionView 集合中执行“重置” 时,该集合为我们提供了一个协调器Drop
。首先,我们报道了协调协调,它destinationIndexPath,即indexPath “-destination”,“复位” Drop
,这就是我们将“复位”。
但是destinationIndexPath可能为nil,因为您可以将“废弃”图像拖到集合Collection View
中不在某些现有单元格之间的位置,因此很可能为nil。如果出现这种情况,那么我创建IndexPath与第0个元素项目在第0部分称为部分。
我可以选择其他任何indexPath,但默认情况下将使用此indexPath。现在我们知道将在哪里执行“重置” Drop
。我们必须仔细阅读协调器提供的所有“可重置” 协调器。此列表中的每个项目都有一个UICollectionViewDropItem TYPE ,可以为我们提供非常有趣的信息。例如,如果我可以从item.sourceIndexPath获取sourceIndexPath,那么我将确定该“拖放” 是从自身self进行的。Drag
,并且拖动的来源Drag
是indexPath等于sourceIndexPath的集合项:在这种情况下,
我什至不必查看localContext来发现此“拖放”是在collectionView 集合内完成的。哇!
现在,我知道了源sourceIndexPath和“目标” destinationIndexPath Drag & Drop
,任务变得很简单。我需要做的就是更新Model,以便交换源和“目标”,然后更新collectionView 集合,在该集合中,您需要使用sourceIndexPath删除集合项,并通过destinationIndexPath将其添加到集合中。我们的本地情况是最简单的,因为在这种情况下,该机制Drag & Drop
不仅适用于同一应用程序,而且适用于同一collectionView 集合,并且我可以使用协调器协调器获取所有必要的信息。。让我们在最简单的本地案例中实现它:
在我们的案例中,我什至不需要localObject,我在创建dragItem时就“隐藏”了它,现在可以从item.localObject形式的item集合中的“ dragated ” 项目中借用。当将Drop
图像“转储” 到同一应用程序中但不是同一collectionView 集合的“垃圾箱”中时,我们将需要它。现在对我来说,两个IndexPath就足够了:源sourceIndexPath和“目的地” destinationIndexPath。我先得到信息关于Image中来自旧位置的图像的 imageInfo,将其从那里删除。然后插入阵列图像的我的模型 imageGallery信息的imageinfo的图像与一个新的索引 destinationIndexPath.item。这就是我更新模型的方式:
现在,我必须更新collectionView集合本身。非常重要的一点是,我不想在“拖放”过程的中间用 reloadData()重载我的collectionView集合中的所有数据,因为它会重新安装我们图库的整个“世界”,这很糟糕,请不要这样做。相反,我将整理并插入元素Drag
单个项:
我删除了带有 sourceIndexPath的collectionView集合项,并插入了带有 destinationIndexPath的新集合项。看起来这段代码很好用,但是实际上,这段代码可以“崩溃”您的应用程序。原因是您对collectionView集合进行了无数次更改,在这种情况下,更改集合的每个步骤都需要与Model正常同步,在我们的情况下不会发生这种情况,因为我们同时执行两项操作:delete和insert。因此,集合 collectionView在某些时候将与模型处于非同步状态。但是有一种非常不错的解决方法,那就是collectionView 集合有一个名为performBatchUpdates的方法,该方法具有一个闭包(closure
),并且在此闭包内,我可以放置任意数量的这些deleteItems,insertItems,moveItems以及我想要的所有东西:
现在deleteItems和insertItems将作为单一操作执行,并且永远不会出现缺少你的模型的同步与集合的CollectionView。最后,我们需要做的最后一件事是要求协调器实现“重置”本身并为其设置动画Drop
:
一旦您从屏幕上抬起手指,图像就会移动,所有操作都同时发生:“重置”,图像消失在一个地方,在另一个地方出现。让我们尝试将图像库中的测试图像“ Venice”移动到第一行的末尾
……并“重置”它:
根据需要,将其放置在第一行的末尾。万岁!
一切正常!现在,我们将不再处理本地情况,即当“重置”元素超出范围时,即来自另一个应用程序。为此,我们在代码中针对sourceIndexPath编写else。如果没有sourceIndexPath,则意味着“ resettable”元素来自OUTSIDE,我们将不得不使用resettabletable item.dragItem.itemProvider元素的itemProver使用数据传输:如果您“拖放” OUTSIDE并“拖放” ”
Drag
Drop
,那么此信息是否立即可用?不,您是从“拖曳的”事物中异步选择数据的。但是,如果样本需要10秒钟怎么办?集合现在会做什么ollection View
?此外,数据可能无法按照我们要求的顺序到达。要对此进行管理并不容易,为此Apple
提出了ollection View
一种使用替代品的全新技术Placeholders
。您将一个Collection View
占位符放置在您的集合中Placeholder
,该集合Collection View
将为您管理所有这些,因此最终选择数据时,您要做的就是让该占位符Placeholder
调用其placeholderContext上下文。并告诉他您已收到信息。然后更新您的Model和上下文placeholderContext自动地将其中一个单元格与占位符交换单元格,这与您收到的数据类型相对应。我们通过创建一个placeholderContext占位符上下文来执行所有这些操作,该占位符上下文管理该占位符,您可以从协调器协调器获得该占位符,并要求您将item元素“重置” 为占位符。我将在placeholderContext占位符上下文中使用初始化程序Placeholder
Placeholder
Drop
Placeholder
将dragItem “抛出” 到UICollectionViewDropPlaceholder:
我要“抛出”的对象Drop
是item.dragItem,其中item是循环的for元素,因为我们可以抛出Drop
很多coordinator.items。我们一一“扔”他们。所以item.dragItem是我们“拖放Drag
”的东西Drop
。此函数的下一个参数是占位符,我将使用UICollectionViewDropPlaceholder初始值设定项创建它:
为此,我需要知道在哪里插入占位符Placeholder
即insertionIndexPath,以及回收的小区的标识符reuseIdentifier。参数insertionIndexPath,很显然,是destinationIndexPath,它IndexPath容纳“拖动”的对象,则在该方法的开始时计算performDropWith。现在让我们看一下reuseIdentifier单元ID。你需要决定什么样的细胞类型细胞是你的一个占位符Placeholder
。协调器协调器没有用于定位器的“预打包” 单元格Placeholder
。必须由您决定这个单元格。因此,从您的请求重用单元的reuseIdentifiercell的标识符,storyboard
以便可以将其用作PROTOTYPE。我将其称为“ DropPlaceholderCell”,但基本上,我可以将其命名为任何名称。这只是我将在我的身上用来创建此东西的String字符串storyboard
。回到我们的位置storyboard
,为占位符创建一个单元格Placeholder
。为此,我们只需要选择一个集合Collection View
并检查它。在第一个字段中,Items
我更改1
为2
。这立即为我们创建了第二个单元格,它是第一个单元格的精确副本。
我们选择新的单元格ImageCell
,将标识符设置为“ DropPlaceholderCell
”,从此处删除所有UI
元素,包括Image View
,因为在图像尚未到达时将使用此PROTOTYPE。我们从“对象面板”中添加了一个新的活动指示器,该指示器Activity Indicator
将旋转,让用户知道我期望一些“重置”数据。同时改变背景颜色Background
要明白,从外部图像时,“复位”的作品正是这种电池单元为原型:
除了新小区的类型不应该ImageCollectionVewCell,因为其中没有图像。我将使该单元格成为普通的UIollectionCiewCellTYPE单元格,因为我们不需要任何Outlets
控制:
让我们配置活动指示器Activity Indicator
,使其从一开始就开始动画设置,而我无需在代码中编写任何内容即可启动它。为此,请点击选项Animating
:仅
此而已。因此,我们对该单元进行了所有设置,然后DropPlaceholderCell
返回到代码。现在我们Placeholder
准备好了一个好的定位器。我们要做的就是获取数据,当接收到数据时,我们只需要告诉placeholderContext有关此上下文的信息,它将交换占位符Placeholder
以及带有数据的“本机”单元格,我们将对模型进行更改。我将“加载”一个对象,该对象将使用loadObject(ofClass:UIImage.self)方法(单数)作为我的项目。我使用的代码item.dragItem.itemProvider供应商itemProvider,这将提供数据元素具有项是异步的。显然,如果连接iitemProvider,对象“复位” 的iItem我们得到这个应用程序之外。以下是loadObject(ofClass:UIImage.self)方法(单数):不在此特定关闭
main queue
。而且,不幸的是,我们不得不切换到main queue
使用DispatchQueue.main.async {}来“捕获”本地AspectRatio变量中图像的长宽比。我们确实引入了两个局部变量imageURL和AspectRatio ...
...,并且在加载图像图像和URL url时将“捕获”它们:
如果局部变量imageURL和AspectRatio都不为nil,我们将使用commitInsertion方法询问placeholderontext占位符上下文给我们一个机会来改变我们的模型imagegallery:
在这个表达式中,我们有insertionIndexPath -它indexPath插入,并改变我们的模型imagegallery。这就是我们所要做的,并且此方法将通过调用普通的cellForItemAt方法自动将一个占位符替换为Placeholder
一个单元格。请注意,insertIndexPath与destinationIndexPath可能有很大不同。怎么了
当然,由于数据采样可能要花费10秒,所以这不太可能,但是可能需要10秒。在这段时间内,收藏中Collection View
可能发生很多事情。可以添加新的细胞的细胞,一切都发生得足够快。始终使用这里insertionIndexPath,只有insertionIndexPath,更新您的模型。我们如何更新模型?我们将把imageModel结构插入到imageGallery.images数组中,该数组由AspectRatio图像的长宽比和相应提供者返回给我们的imageURL图像URL组成。这将更新我们的imageGallery Model ,而commitInsertion方法将为我们完成其余的工作。您不再需要做任何额外的事情,没有插入,没有删除等等。而且,由于我们处于封闭状态,因此我们需要添加自我。 。
如果我们因为某些原因无法得到长宽比的aspectRatio和URL
图像IMAGEURL从相应的供应商,可能已经收到错误的错误,而不是由供应商,我们必须让他们知道的情况下placeholderContext,你需要摧毁这个占位符Placeholder
,因为我们都是一样的,我们不能获取其他数据:
要记住的一件事是URLs
,它们来自这样的地方Google
;实际上,它们需要进行细微的转换才能“干净”URL
为图像。如何解决这个问题可以在此示例应用程序到文件中可以看到Utilities.swift
在Github上。因此,在接收URL
图像时,我们使用URL类中的imageURL属性:这就是您要接受集合中其他内容的全部步骤。让我们来看看它的作用。我们同时在多任务模式下启动演示应用程序,并使用搜索引擎。正如我们寻找图片中的“黎明»(日出)的主题。在已经建立
Collection View
ImageGallery
Safari
Google
Google
Safari
Drag & Drop
机制,因此我们可以选择其中一张图像,将其放置很长一段时间,将其稍微移动一下,然后将其拖到我们的图片库中。
绿色加号“ +”的存在表示我们的应用程序已准备就绪,可以接受第三方图像并将其复制到用户指定位置的集合中。 “重置”图像之后,需要花费一些时间来下载图像,并且此时它可以正常工作Placeholder
:
下载完成后,“重置”图像将放置在正确的位置,然后Placeholder
消失:
我们可以继续“重置”图像并将其放置在更多图像的集合:
在“重置”工作之后Placeholder
:
结果,我们的图像库中充满了新图像:
现在,很显然,我们能够拍照从外面看,我们并不需要测试图像,我们将移除:
我们viewDidLoad中就变得很简单:这是我们正在做我们的Controller
Drag
,并Drop
委派并添加识别手势掐,其中规定每行图像的数量:
当然我们可以添加缓存图像ImageCache:
我们将填充ImageCache时“复位” Drop
的方法中performDrop ...
和在用户类“网络”的样品ImageCollectionViewCell:
A中所使用的高速缓存ImageCache将播放细胞细胞在我们的自定义类的图像画廊 ImageCollectionViewCell:
现在我们开始与空集...
...那么“抛”的新形象我们收集...
... prishodit图像加载和Placeholder
运行...
...并在正确的地方的形象出现:
我们继续来填补我们的收藏OUTSIDE:
prishodit加载图像的Placeholders
作品...
图像出现在正确的位置:
因此,我们可以使用图像库做很多事情:外部填充图像,内部重新组织项目,与其他应用程序共享图像下宫我们只需要教她如何通过“重置”它们来消除不必要的图像Drop
在右侧导航栏上显示的“垃圾箱”中。如“图库演示应用程序的功能”部分所述,“垃圾桶”由GabageView类表示,该类继承自UIView,我们必须教它接受我们收藏的图像ollection View
。将Drop
图库图像重置为垃圾桶。
立即从该地方-到采石场。我将向GabageView添加一个“交互” 交互,这将是一个UIDropInteraction,因为我试图对Drop
某种事物进行“重置” 。我们需要提供的UIDropInteraction是一个委托委托,我要将自己self分配给该委托委托:
自然地,我们的GabageView类必须确认我们正在实现UIDropInteractionDelegate协议:
要使其正常工作Drop
,我们需要做的一切,这是为了实现我们已经知道的canHandle方法,sessionDidUpdate和 performDrop。
但是,与类似的集合方法不同Collection View
,我们没有任何其他信息以转储位置的 indexPath形式出现。让我们实现这些方法。内的方法 canHandle将被处理只有“拖放”Drag
,其表示将图像一个UIImage。因此,仅当 session.canLoadObjects(ofClass:UIImage.self)时,我才返回 true:在 canHandle方法中,您实际上只是在说“可拖动”对象不是 UIImage图像。
,那么继续“重置” Drop并调用后续方法毫无意义。如果“可拖动”对象是UIImage图像,那么我们将执行sessionDidUpdate方法。在此方法中,我们需要做的就是返回UIDropProposal “重置”商品Drop
。而且我已经准备好只接受图像的UIImage TYPE的“拖放式” LOCALLY对象,可以将其“拖放” Drop
到GarbageView内的任何位置。我的GarbageView将不会与外部转储的图像进行交互。所以我正在使用session.localDragSession变量进行解析是否存在本地“重置” Drop
,并且我以UIDropProposal构造函数的形式返回“重置”语句,并且操作参数的值为.copy,因为Drag
我的应用程序中的ALWAYS LOCAL“拖放” 将来自该集合Collection View
。如果在外部发生“拖放” Drag
和“重置” Drop
,那么我将以UIDropProposal构造函数的形式返回“重置”语句,其操作参数的值为.fobbiden,即“ forbidden”,我们将得到一个“ reset”禁止符号,而不是绿色加号。 。
复制UIImage图像,我们将模拟将其缩放比例减小到几乎为0,并且当“重置”发生时,我们将从集合中移除此图片Collection View
。要创建用户的幻觉“复位,并消失”图像中的“垃圾桶”,我们使用我们的新方法previewForDropping,它允许你重定向“复位” Drop
在另一个地方,并在同一时间在动画过程中变换了“一次性”的对象:
在在此方法中,使用UIDragPreviewTarget初始化程序,我们为要删除的目标对象获取了一个新的preView,并使用retargetedPreview方法将其重定向到一个新的位置,到“垃圾桶”,其比例减小到几乎为零:
如果用户抬起手指,就会发生“重置” Drop
,我(像GarbageView一样)收到了performDrop消息。在performDrop消息中,我们执行实际的“重置” Drop
。老实说,由于我们将使它变为几乎不可见,因此转储到GarbageView本身上的图像不再使我们感兴趣,很可能“重置”完成这一事实实际上Drop
表明我们已将该图像从集合中删除Collection View
。为了做到这一点,我们必须知道集合本身和集合indexPath丢弃其中的图像。我们从哪里可以得到它们?由于这一过程Drag & Drop
发生在一个单一的应用程序,它提供给我们所有的地方:本地Drag
会话localDragSession我们的Drop
会话的会话,本地环境localContext,这是我们的收集sollectionView和本地对象localObject,我们可以自己做复位图像图像从“画廊”或indexPath。正因为如此,我们可以将方法得到performDrop类GarbageView集收集,并用它数据源如何 ImageGalleryCollectionViewController和型号 imageGallery我们Controller
,我们可以得到的图像阵列图像类型[ImageModel]:
随着本地的帮助Drag
会话 localDragSession我们Drop
会议会议我们能够得到所有的“拖” GarbageView Drag
的元素项,并有可能成为很多,因为我们知道,和它们都是我们 collectionView集合的图像。创建Drag
元素 dragItems我们的集合Collection View
,我们对每个“拖”Drag
元素dragItem本地对象 localObject,是谁的图像图像,但它是我们内部重组的收集过程中不派上用场的CollectionView,但“复位”图片库“垃圾桶”我们迫切需要在本地设备 localObject “拖动”对象 dragItem,毕竟这一次我们没有协调员,他如此慷慨地分享有关collectionView集合中发生的事情的信息。因此,我们希望 localObject成为模型图片的images数组中的 indexPath索引imageGallery。制作的方法进行必要的修改 dragItems(在indexPath:IndexPath)类 ImageGalleryCollectionViewController:
现在我们可以把每一个“pretaskivaemogo”元素项目它 localObject,这是该指数 indexPath图像阵列图像我们的模型 imagegallery,并将其发送到数组索引的索引和删除图像的 indexPahes数组:在 performBatchUpdates方法中
了解索引索引的数组和删除图像的 indexPahes的数组收集收集 Google移除型号所有被删除的图片图像,并从集合集合:
运行应用程序,填补了新的图像画廊:
选择一个图像对,我们想从我们的库中删除...
...“扔”他们在图标上的“垃圾桶” ......
他们几乎减少到0 ...
并从集合中消失Collection View
,隐藏在“垃圾桶”中:
开始之间保存图像。
为了在运行之间保存图像库,在将模型转换成格式后,我们将使用UserDefaultsJSON
。要做到这一点,我们将增加我们的Controller
变量VAR defailts ...
...,并在模型ImageGallery和ImageModel协议可编码:
字符串字符串,数组的数组,网址,和双已在执行该协议可编码的,所以我们没有什么别的事情可做去工作编码和解码的模型ImageGallery的JSON
格式。我们如何获得ImageGallery的JSON
版本?为了创建这个扩展变量VAR JSON,它返回尝试将其转换,结果自我,通过JSONEncoder.encode()的格式:这就是全部。将self转换为format 的结果将返回Data,或者如果此转换失败将返回nil,尽管后者永远不会发生,因为此TYPE是100%可编码的。使用可选的变量JSON只是为了对称的原因。JSON

JSON
现在,我们有一种方法可以将ImageGallery模型转换为数据格式JSON
。json变量是否具有TYPE 数据?可以在UserDefaults中记住。现在想象一下,以某种方式我们设法获取了jsonJSON
数据,我想根据它们重新创建Model,即ImageGallery结构的实例。为此,为ImageGallery编写一个INITIALIZER非常容易,其输入参数为json data 。此初始化程序将是“下降”初始化程序(JSON
failable
)如果初始化失败,则崩溃并返回nil:
我只是使用JSONDecoder解码器获取newValue值,尝试对传递给我的初始化程序的json数据进行解码,然后将其分配为self。如果成功完成此操作,则将获得一个新的ImageGallery实例,但是如果尝试失败,则将返回nil,因为初始化“失败”。我必须说,这里我们有更多“失败”()的原因,因为json数据很有可能fail
JSON
可能变坏或变空,所有这些都可能导致fail
初始化程序的“掉落”()。现在,我们可以实现所读取的JSON
数据和恢复模型imagegallery方法viewWillAppear中我们Controller
...
...以及观察者的条目didSet {}性能imagegallery:
让我们来运行应用程序,并填写我们的形象画廊:
如果我们关闭应用程序,然后再次打开它,我们可以看到我们以前的画廊保存在UserDefaults中的图像。结论
本文通过一个非常简单的演示应用程序“ Image Gallery”的示例演示将技术集成Drag & Drop
到iOS
应用程序中有多么容易。这样就可以完全编辑图库,从那里的其他应用程序“投掷”新图像,移动现有图像并删除不必要的图像。并将分布在图库中的图像分发到其他应用程序。当然,我们希望创建许多这样的主题如诗如画的图像集合,并将其直接保存到iPad或iCloud Drive。如果每个此类图库都被解释为永久存储的UIDocument,则可以这样做。这样的解释将使我们能够提升到下一个抽象层次,并创建一个可处理文档的应用程序。在这样的应用程序中,DocumentBrowserViewController组件将显示您的文档,与该应用程序非常相似Files
。这将使您可以在自己的和上创建“ Image Gallery”类型的UIDocument图像,以及选择所需的文档进行查看和编辑。但这是下一篇文章的主题。PS该机制实现之前和之后的演示应用程序代码都在Github上。iPad
iCloud Drive
Drag & Drop