在您的iOS应用中拖放



iOS 11iOS 12上运行的Drag & Drop机制是一种在单个应用程序内以及不同应用程序之间以图形方式异步复制或移动数据的方法。 尽管这项技术已有30多年的历史了,但实际上它已成为iOS上的“突破性”技术,因为在iOS拖动某些内容时, multitouch允许您与系统的其余部分自由交互并收集数据以从不同的应用程序重置。

iOS可以一次捕获多个元素。 此外,它们不必处于方便的选择范围内:您可以拿第一个对象,然后转到另一个应用程序并抓取其他东西-所有对象将被收集在手指下的“桩”中。 然后在屏幕上调用通用扩展坞,打开那里的任何应用程序并捕获第三个对象,然后在运行应用程序的情况下进入屏幕,并且在不释放对象的情况下将它们转储到一个打开的程序中。 这样的行动自由在iPad上, iPhone上是可能的, iOS Drag & Drop范围仅限于一个应用程序的框架。

最流行的应用程序( SafaryChromeIbisPaint XMailPhotosFiles等)已经具有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中收集的图像,例如NotesNotesMail或照片库( 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!微调框活动指示器:UIActivityIndi​​catorView! 并由UICollectionViewCell类的自定义subclass ImageCollectionViewCell支持:



ImageCollectionViewCellPublic APIimageURL图像URL 。 一旦安装它,我们的UI更新,即,在该imageURL上异步选择图像的数据并显示在单元格中。 从网络中检索数据时, 微调器活动指示器正在工作,表明我们正在检索数据。

我使用带有qos服务质量参数的全局队列(qos:.userInitiated)来获取给定URL数据,该URL设置为.userInitiated,因为我是根据用户的请求选择数据的:



每次在闭包中使用自己的变量时(在我们的示例中为imageViewimageURL) ,编译器都会强制您将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范围



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



在我们的应用程序中实现Drag & Drop机制的计划如下:

  1. 首先,我们将使“ Collection View “拖动” UIImage图像Collection View能力,无论是在内部还是外部,
  2. 然后我们将教我们的“ Collection View图像集合接受从UIImage外部或本地进行“拖动” Drag
  3. 我们还将通过垃圾桶按钮教我们的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 ,我们将“拖放”。 对于我们来说,这确实非常方便,因为它是负责使用sessionindexPath参数确定如何处理Drag

如果返回“ draggable ”元素的数组[UIDragItems], Drag初始化Drag的“拖动”;如果返回空数组[]Drag忽略Drag的“拖动”。

我将使用indexPath参数创建一个小的私有 dragItems函数(位于:indexPath) 。 它返回我们需要的数组[UIDragItem]



拖放式UIDragItem是什么样的
他只有一件非常重要的东西叫做itemProvideritemProvider只是可以提供将被拖动的数据的东西。

您有权问:“对根本没有数据的UIDragItem元素进行“拖放”怎么样?” 例如,您要拖动的项目可能没有数据,因为创建此数据是一项昂贵的操作。 这可能是图像图像或需要从Internet下载数据的东西。 很棒的事情是Drag & Drop操作是完全异步的。 当您开始拖放Drag ,它实际上是一个非常轻巧的对象( lift preview ),将其拖动到任何地方,在此“拖动”过程中什么也没有发生。 但是,一旦您将对象“拖放”到某个地方,然后成为itemProvider ,它实际上必须为“拖放”和“抛出”的对象提供真实数据,即使它需要一定的时间。

幸运的是,有许多内置的itemProviders 。 这些是iOS中已经存在的类,它们是itemPoviders ,例如NSString ,它允许您拖放没有字体的文本。 当然,这是一个UIImage图像。 您可以选择并拖放UIImages图像。 NSURL类,这绝对是很棒的。 您可以转到网页,选择URL然后将其“拖放”到所需位置。 这可以是文章的链接,也可以是图像的URL ,就像在我们的演示中一样。 这些是地址簿中的UIColorMKMapItem地图元素CNContact联系人的颜色类,您可以选择并拖动很多东西。 它们都是itemProviders

我们将“拖放” UIImage图像。 它位于带有indexPath的“ Collection View单元格中,它可以帮助我选择该单元 ,从中获取Outlet imageView并获取其图像

让我们用几行代码来表达这个想法。
首先,我向Collection View请求有关与此indexPath对应的item元素的单元格信息



Collection ViewcellForItem(位于IndexPath)方法仅适用于可见的单元格,但是,在我们的情况下当然可以使用,因为我在屏幕上“拖放”了Drag集合元素,并且它是可见的。

因此,我得到了一个“可拖动”的细胞
接下来,我使用as运算符 到此单元格,以便它具有我的自定义subclass的TYPE。 如果这可行,那么我将获得一个Outlet imageView ,从中获取其图像image 。 我只是“捕获”了该indexPath的图像。

现在,我有了一个图像图像 ,我所要做的就是使用结果图像图像作为itemProvider创建这些UIDragItems之一, 即为我们提供数据的东西。
我可以使用UIDragItem构造函数创建dragItem ,该构造函数将itemProvider作为参数:



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



您只需将对象对象提供给NSItemProvider构造函数,它就知道如何使itemProvider成为对象。 作为这样的对象,我给图像提供了从单元接收到的图像 ,并获得了UIImage的 itemProvider
仅此而已。 我们创建了dragItem ,并将其作为具有一个元素的数组返回。

但在此之前我回dragItem,我打算做一两件事,即设置变量localObjectdragItem,等于产生的画面图像



这是什么意思?
如果Drag在本地(即在应用程序内部)执行“拖放” ,则无需通过异步数据检索来遍历itemProvider相关联的所有代码。您不需要这样做,只需要使用localObject并使用它即可。这是一种带有局部“拖放”的“短路” Drag

我们编写的代码在拖放时将起作用Drag集合之外的Collection View其他应用程序,但是如果Drag在本地拖放,则可以使用localObject。接下来,我返回一个包含dragItem元素的数组

顺便说一句,如果由于某种原因我无法获得该单元图像,则返回一个空数组[],这意味着取消了“拖放” 操作。除了本地对象localObject,你能记住的地方背景localContext我们的会话的会话。在我们的例子中,它将是一个collectionView 集合Drag



Drag它是对我们有用算账:



具有“拖放” Drag,你可以增加更多项目的项目这个“拖放”,只是做姿态水龙头上他们。因此,您可以Drag一次拖放许多项目。而且,这很容易通过另一个委托方法UICollectionViewDragDelegate来实现,该方法itemsForestegingning方法非常相似,该方法名为itemsForAddingToitemsForAddingTo方法看起来与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部件,还需要实现另外两个强烈推荐的方法。这是canHandledropSessionDidUpdate



如果我们实现这两种方法,那么当我们将图像从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,这表示图像的UIImageURL这个形象,所以我们不打算只保留图像本身的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实现复位DragCollection ViewDrop

DropDropiOSDrop

在此方法中,我们必须返回一个Drop句子,句子operation参数可以具有.copy.move.cancel.forbidden值。这些都是我们在处理常规UIView时通常所具有的所有可能性但收集更进一步,并提供返回专门优惠UICollectionViewDropProposal,这是一种UIDropProposal,并允许除手术操作指定的附加参数意图的收集参量

Collection ViewsubclassCollection 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,并且拖动的来源DragindexPath等于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),并且在此闭包内,我可以放置任意数量的这些deleteItemsinsertItemsmoveItems以及我想要的所有东西:



现在deleteItemsinsertItems将作为单一操作执行,并且永远不会出现缺少你的模型的同步与集合的CollectionView

最后,我们需要做的最后一件事是要求协调器实现“重置”本身并为其设置动画Drop



一旦您从屏幕上抬起手指,图像就会移动,所有操作都同时发生:“重置”,图像消失在一个地方,在另一个地方出现。
让我们尝试将图像库中的测试图像“ Venice”移动到第一行的末尾



……并“重置”它:



根据需要,将其放置在第一行的末尾。
万岁!一切正常!

现在,我们将不再处理本地情况,即当“重置”元素超出范围时,即来自另一个应用程序。
为此,我们在代码中针对sourceIndexPath编写else。如果没有sourceIndexPath,则意味着“ resettable”元素来自OUTSIDE,我们将不得不使用resettabletable item.dragItem.itemProvider元素itemProver使用数据传输如果您“拖放” OUTSIDE并“拖放” ”



DragDrop,那么此信息是否立即可用?不,您是从“拖曳的”事物中异步选择数据的。但是,如果样本需要10秒钟怎么办?集合现在会做什么ollection View?此外,数据可能无法按照我们要求的顺序到达。要对此进行管理并不容易,为此Apple提出了ollection View一种使用替代品的全新技术Placeholders

您将一个Collection View占位符放置在您的集合中Placeholder,该集合Collection View将为您管理所有这些,因此最终选择数据时,您要做的就是让该占位符Placeholder调用其placeholderContext上下文。并告诉他您已收到信息。然后更新您的Model和上下文placeholderContext自动地其中一个单元格占位符交换单元,这与您收到的数据类型相对应。我们通过创建一个placeholderContext占位符上下文执行所有这些操作,该占位符上下文管理该占位符,您可以从协调器协调器获得该占位符,要求您item元素“重置” 为占位符我将在placeholderContext占位符上下文中使用初始化程序Placeholder

PlaceholderDropPlaceholder

dragItem “抛出” UICollectionViewDropPlaceholder



我要“抛出”的对象Dropitem.dragItem,其中item循环for元素,因为我们可以抛出Drop很多coordinator.items。我们一一“扔”他们。所以item.dragItem是我们“拖放Drag”的东西Drop。此函数的下一个参数是占位符,我将使用UICollectionViewDropPlaceholder初始值设定项创建它



为此,我需要知道在哪里插入占位符PlaceholderinsertionIndexPath,以及回收的小区的标识符reuseIdentifier
参数insertionIndexPath,很显然,是destinationIndexPath,它IndexPath容纳“拖动”的对象,则在该方法的开始时计算performDropWith

现在让我们看一下reuseIdentifier单元ID。你需要决定什么样的细胞类型细胞是你的一个占位符Placeholder。协调器协调器没有用于定位器的“预打包单元格Placeholder。必须由您决定这个单元。因此,从您的请求重用单元的reuseIdentifiercell的标识符,storyboard以便可以将其用作PROTOTYPE。

我将其称为“ DropPlaceholderCell”,但基本上,我可以将其命名为任何名称。
这只是我将在我的身上用来创建此东西String字符串storyboard
回到我们的位置storyboard为占位符创建一个单元Placeholder。为此,我们只需要选择一个集合Collection View并检查它。在第一个字段中,Items我更改12。这立即为我们创建了第二个单元格,它是第一个单元格的精确副本。



我们选择新的单元格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变量中图像的长宽比

我们确实引入了两个局部变量imageURLAspectRatio ...



...,并且在加载图像图像和URL url时将“捕获”它们



如果局部变量imageURLAspectRatio都不为nil,我们将使用commitInsertion方法询问placeholderontext占位符上下文给我们一个机会来改变我们的模型imagegallery



在这个表达式中,我们有insertionIndexPath -它indexPath插入,并改变我们的模型imagegallery。这就是我们所要做的,并且此方法将通过调用普通的cellForItemAt方法自动将一个占位符替换为Placeholder一个单元格请注意,insertIndexPathdestinationIndexPath可能有很大不同

怎么了当然,由于数据采样可能要花费10秒,所以这不太可能,但是可能需要10秒。在这段时间内,收藏中Collection View可能发生很多事情。可以添加新的细胞的细胞,一切都发生得足够快。

始终使用这里insertionIndexPath,只有insertionIndexPath,更新您的模型。

我们如何更新模型?

我们将把imageModel结构插入到imageGallery.images数组中该数组AspectRatio图像的长宽比相应提供者返回给我们imageURL图像URL组成

这将更新我们的imageGallery Model ,而commitInsertion方法将为我们完成其余的工作。您不再需要做任何额外的事情,没有插入,没有删除等等。而且,由于我们处于封闭状态,因此我们需要添加自我。



如果我们因为某些原因无法得到长宽比的aspectRatioURL图像IMAGEURL从相应的供应商,可能已经收到错误的错误,而不是由供应商,我们必须让他们知道的情况下placeholderContext,你需要摧毁这个占位符Placeholder,因为我们都是一样的,我们不能获取其他数据:



要记住的一件事是URLs,它们来自这样的地方Google;实际上,它们需要进行细微的转换才能“干净”URL为图像。如何解决这个问题可以在此示例应用程序到文件中可以看到Utilities.swiftGithub上
因此,在接收URL图像时,我们使用URL类中imageURL属性这就是您要接受集合中其他内容的全部步骤让我们来看看它的作用。我们同时在多任务模式下启动演示应用程序,使用搜索引擎。正如我们寻找图片中的“黎明»(日出)的主题。在已经建立



Collection View

ImageGallerySafariGoogleGoogleSafariDrag & 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对象,可以将其“拖放” DropGarbageView内的任何位置。我的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。正因为如此,我们可以将方法得到performDropGarbageView收集,并用它数据源如何 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 ...



...,并在模型ImageGalleryImageModel协议可编码



字符串字符串,数组的数组网址,和已在执行该协议可编码的,所以我们没有什么别的事情可做去工作编码和解码的模型ImageGalleryJSON格式。
我们如何获得ImageGalleryJSON版本为了创建这个扩展变量VAR JSON,它返回尝试将其转换,结果自我,通过JSONEncoder.encode()格式:这就是全部。self转换为format 的结果将返回Data,或者如果此转换失败将返回nil,尽管后者永远不会发生,因为此TYPE是100%可编码的。使用可选的变量JSON只是为了对称的原因。
JSON



JSON
现在,我们有一种方法可以将ImageGallery模型转换数据格式JSONjson变量是否具有TYPE 数据?可以在UserDefaults中记住
现在想象一下,以某种方式我们设法获取了jsonJSON数据,我想根据它们重新创建Model,即ImageGallery结构的实例。为此,为ImageGallery编写一个INITIALIZER非常容易,其输入参数为json data 。此初始化程序将是“下降”初始化程序(JSONfailable)如果初始化失败,则崩溃并返回nil



我只是使用JSONDecoder解码器获取newValue,尝试对传递给我的初始化程序json数据进行解码,然后将其分配为self如果成功完成此操作,则将获得一个新的ImageGallery实例,但是如果尝试失败,则将返回nil,因为初始化“失败”。我必须说,这里我们有更多“失败”()的原因,因为json数据很有可能

failJSON可能变坏或变空,所有这些都可能导致fail初始化程序的“掉落”()。

现在,我们可以实现所读取的JSON数据和恢复模型imagegallery方法viewWillAppear中我们Controller...



...以及观察者的条目didSet {}性能imagegallery



让我们来运行应用程序,并填写我们的形象画廊:



如果我们关闭应用程序,然后再次打开它,我们可以看到我们以前的画廊保存在UserDefaults中的图像

结论


本文通过一个非常简单的演示应用程序“ Image Gallery”的示例演示将技术集成Drag & DropiOS应用程序中有多么容易。这样就可以完全编辑图库,从那里的其他应用程序“投掷”新图像,移动现有图像并删除不必要的图像。并将分布在图库中的图像分发到其他应用程序。

当然,我们希望创建许多这样的主题如诗如画的图像集合,并将其直接保存到iPad或iCloud Drive。如果每个此类图库都被解释为永久存储的UIDocument,则可以这样做这样的解释将使我们能够提升到下一个抽象层次,并创建一个可处理文档的应用程序。在这样的应用程序中,DocumentBrowserViewController组件将显示您的文档,与该应用程序非常相似Files这将使您可以在自己的和上创建“ Image Gallery”类型的UIDocument图像,以及选择所需的文档进行查看和编辑。但这是下一篇文章的主题。PS该机制实现之前和之后的演示应用程序代码都在Github上iPadiCloud Drive


Drag & Drop


Source: https://habr.com/ru/post/zh-CN425817/


All Articles