UICollectionView 的 拖放(dragDelegate&dropDelegate)

UICollectionView 支持拖放操作,该API处理所显示的 Items 。为了支持拖动,定义一个 UICollectionViewDragDelegate 拖动委托对象,并将其分配给集合视图的 dragDelegate 属性。要处理掉落,定义一个 UICollectionViewDropDelegate 协议的对象,并将其分配给集合视图的 dropDelegate 属性。

一、从集合视图中拖动 Items

集合视图管理大多数与拖动相关的交互,但是您需要指定要拖动哪些项。当拖动手势发生时,集合视图创建一个拖动会话并调用您的拖动委托对象的 collectionView:itemsForBeginningDragSession:atIndexPath: 方法。如果从该方法返回一个非空数组,则集合视图将开始拖动指定的项。当不允许用户从指定索引路径中拖动项时,返回空数组。

注意:
使用 UICollectionViewDragDelegate 协议的其他方法来管理其他与拖动相关的交互。例如,可以自定义正在拖动的项目的外观,并允许用户将项目添加到当前拖动会话中。

在实现collectionView:itemsForBeginningDragSession:atIndexPath方法时,请执行以下操作:
    1. 创建一个或多个 NSItemProvider 对象。使用项提供程序来表示集合视图项的数据。
    1. 将每个项目提供程序对象包装在 UIDragItem 对象中。
  • 3.考虑为每个拖动项的 localObject 属性赋值。这个步骤是可选的,但是在同一个应用程序中拖放内容会更快。

  • 4.从方法返回拖动项。

使用提供的索引路径来确定要拖动的项。如果项目是当前选定项目集合的一部分,则集合视图将自动拖动所有选定项目。如果项目不是当前选择的一部分,则集合视图将其添加到拖动操作中。

有关初始化拖动的更多信息,请参见UICollectionViewDragDelegate

二、接收放置的内容

当内容被拖到它的边界内时,集合视图会咨询它的拖放委托,以确定它是否可以接收被拖放的数据。最初,集合视图只调用拖放委托的 collectionView:canHandleDropSession: 方法,以确定是否可以将指定的数据合并到数据源中。如果可以合并数据,则集合视图会开始调用其他方法来确定数据可以放在哪里。

当用户的手指移动时,集合视图会跟踪潜在的放置位置,并通过调用每次更改的委托 collectionView:dropSessionDidUpdate:withDestinationIndexPath: 方法来通知您的代理。实现此方法是可选的,但推荐使用,因为它允许集合视图显示关于如何合并拖动项目的视觉反馈。在您的实现中,创建一个 UICollectionViewDropProposal 对象,其中包含有关如何响应指定索引路径上的放置的信息。例如,您可能希望将内容作为新 item 插入到数据源中,或者将数据添加到指定索引路径处的现有 item 中。因为该方法被频繁调用,所以要尽可能快地返回您的建议。如果不实现此方法,则集合视图不会提供有关如何处理放置的视觉反馈。

在 collectionView:performDropWithCoordinator: 方法的实现中,请执行以下操作:
  • 1、对所提供的放置协调器对象中的 items 属性进行迭代。

  • 2、对于每个项目,确定要如何处理其内容:

    • 2.1、如果 itemsourceIndexPath 包含一个值,则该 item 起源于集合视图。使用批处理更新将 item 从当前位置删除,并将其插入到新的索引路径。
    • 2.2、如果设置了拖动项的 localObject 属性,则该 item 来自应用程序中的其他位置,因此必须插入 item 或更新现有 item
    • 2.3、如果没有其他可用的选项,在拖拽 itemitemProvider 属性中使用 NSItemProvider 来异步获取数据并插入或更新 item
  • 3、更新数据源,并在集合视图中插入或移动必要的项。

对于已经在应用程序本地的 items,通常可以直接更新集合视图的数据源和界面。例如,您可以使用批处理更新来删除然后插入来自集合视图的 item。完成后,调用放置协调器的 dropItem:toItemAtIndexPath: 方法,将拖动的内容插入到集合视图中。

对于必须使用 NSItemProvider 对象检索的数据,请在集合视图中插入占位符,直到能够检索实际数据为止。只有在向集合视图中插入新 item 时,才需要插入占位符。占位符在集合视图中充当临时 item,在实际数据可用之前显示您想要显示的默认内容。例如,您可以提供一个占位符单元格,其中包含一个文本字段,说明当前正在加载内容。

要在集合视图中插入占位符,请执行以下操作:
  • 1、调用提供的 UICollectionViewDropCoordinator 对象的 dropItem:toPlaceholder: 方法将占位符单元格插入到集合视图中。

  • 2、开始从 NSItemProvider 对象异步加载数据。

NSItemProvider 对象返回实际数据时,提交插入并将占位符单元格替换为最终单元格。具体来说,调用创建占位符后收到的上下文对象的 commitInsertionWithDataSourceUpdates: 方法。在传递给该方法的块中,更新模型对象和集合视图的数据源。当此方法返回时,集合视图将自动删除占位符并插入最终项,从而使更新后的数据反映在新项中。

在删除协调器的 destinationIndexPath 属性指定的位置插入占位符。

三、UICollectionViewDragDelegate

在用于从集合视图中初始化拖动的对象中实现此协议。该协议唯一需要的方法是 collectionView:itemsForBeginningDragSession:atIndexPath: 方法,但是您可以根据需要实现其他方法来定制集合视图的拖动行为。

将自定义委托对象分配给集合视图的 dragDelegate 属性。

API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@protocol UICollectionViewDragDelegate <NSObject>
//必须实现的方法
@required
//提供 items 以 开始 与 给定 indexPath 关联的拖动。
//如果返回 空数组,则 不会开始拖动会话。
//提供要拖动的初始 items(如果有)。 
// session :当前拖动会话对象。 
// indexPath 要拖动的item的索引路径。 
//UIDragItem 对象数组,其中包含要拖动的项的详细信息。
//返回空数组以防止拖动项。

//注意:
//必须实现此方法才能从集合视图中拖动项。
//在实现中,为指定 indexPath 处的项创建一个或多个 UIDragItem 对象。
//通常,您只返回一个拖动 item,但是如果指定的项有 子item 或者
//没有一个或多个关联 item 就不能拖动,那么也要包含这些 items。

//当在集合视图的边界内开始新的拖动时,集合视图调用此方法 一次或多次。
//具体来说,
//如果用户从选定项开始拖动,则集合视图会为所选item中的 每个项 调用 此方法一次。
//如果用户从未选中的item开始拖动,则集合视图仅为该项调用该方法一次。
- (NSArray<UIDragItem *> *)collectionView:(UICollectionView *)collectionView 
    itemsForBeginningDragSession:(id<UIDragSession>)session 
          atIndexPath:(NSIndexPath *)indexPath;

//以下是可实现方法,非必须
@optional
//注意:
//添加 item 手势,响应请求将 item 添加到现有拖动会话中。
//如果需要,您可以使用提供的点(在集合视图的坐标空间中)进行额外的命中测试。
//如果没有实现,或者返回一个空数组,
//则在拖动中将不会添加任何项,手势将被正常处理。

//将指定 item 添加到现有拖动会话中。
//session:当前拖动会话对象。
//indexPath:要添加到拖动中的 item 的索引路径。 
//point:用户触摸的点。该点位于集合视图的坐标空间中。
//返回 UIDragItem对象数组 :其中包含要添加到当前拖动会话的 item。
//返回空数组以防止将 item 添加到拖动会话。

//注意:当您希望允许用户向一个在活动状态的拖动会话中添加 item 时,请实现此方法。
//如果你不实现这个方法,在集合视图中的点击 可以触发 items 的选择,或其他行为。
//但是,当拖动会话处于在活动状态并且发生点击时,集合视图将会调用此方法,
//以便您有机会将基础 item 添加到拖动会话中。
//在您的实现中,为指定的 indexPath 的 item 创建一个或多个 UIDragItem 对象。
//通常,您只返回一个拖动项,
//但是如果指定的item有子item或者没有一个或多个关联item就不能拖动,
//那么也要包含这些item。
- (NSArray<UIDragItem *> *)collectionView:(UICollectionView *)collectionView 
  itemsForAddingToDragSession:(id<UIDragSession>)session 
    atIndexPath:(NSIndexPath *)indexPath 
         point:(CGPoint)point;
#pragma mark --------- 跟踪拖动会话 ----------
//通知您即将开始对集合视图进行拖动会话。
//在拖动的位置发生变化之前调用。
- (void)collectionView:(UICollectionView *)collectionView 
       dragSessionWillBegin:(id<UIDragSession>)session;
//通知您集合视图的拖动会话已结束。
- (void)collectionView:(UICollectionView *)collectionView 
        dragSessionDidEnd:(id<UIDragSession>)session;

#pragma mark --------- 提供自定义预览 ----------
//返回 有关如何在拖动期间在指定位置显示项目 的自定义信息。
// 拖动参数,指示在拖动过程中如何显示单元格的内容。 
- (nullable UIDragPreviewParameters *)collectionView:(UICollectionView *)collectionView  
    dragPreviewParametersForItemAtIndexPath:(NSIndexPath *)indexPath;

#pragma mark --------- 控制拖动会话 ----------
//该值确定是否允许拖动会话进行移动操作。
//如果没有实现,这将默认为YES。
- (BOOL)collectionView:(UICollectionView *)collectionView 
     dragSessionAllowsMoveOperation:(id<UIDragSession>)session;

//返回一个布尔值,用于确定拖放会话的源应用程序和目标应用程序是否必须相同。
- (BOOL)collectionView:(UICollectionView *)collectionView 
  dragSessionIsRestrictedToDraggingApplication:(id<UIDragSession>)session;

@end

四、UICollectionViewDropDelegate

在用于将删除的数据合并到集合视图中的对象中实现此协议。
这个协议唯一需要的方法是collectionView:performDropWithCoordinator: method,但是,您可以根据需要实现其他方法来自定义集合视图的删除行为。将自定义委托对象分配给集合视图的dropDelegate属性。

API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@protocol UICollectionViewDropDelegate <NSObject>

@required
#pragma mark --------- 合并被拖放的item ----------
// 告诉您的代理将放置数据合并到集合视图中。 
// collectionView 接收投递的集合视图。
// coordinator  处理拖放时要使用的协调器对象。
// 使用此对象可以将自定义行为与集合视图的默认行为相协调。
//使用此方法接受删除的内容并将其集成到集合视图中。在您的实现中,
//遍历协调器对象(coordinator)的items属性并从每个UIDragItem中获取数据。
//将数据合并到集合视图的数据源中,并通过插入任何所需的项来更新集合视图本身。
//合并项目时,
//请使用协调器对象的方法设置从拖动项目的预览到集合视图中相应项目的转换动画。
//对于立即合并的项,可以使用dropItem:toTarget:
//或dropItem:toPItemAtIndexPath:方法来执行动画。
- (void)collectionView:(UICollectionView *)collectionView 
  performDropWithCoordinator:(id<UICollectionViewDropCoordinator>)coordinator;


@optional

#pragma mark --------- 声明支持处理掉落 ----------
//询问dropDelegate,集合视图是否可以接受具有指定类型数据的删除。
//如果集合视图可以接受拖放的数据,则为YES;
//如果不能,则为NO,将不再调用此drop会话的委派方法。
//如果没有实现此方法,默认值为YES。
- (BOOL)collectionView:(UICollectionView *)collectionView 
     canHandleDropSession:(id<UIDropSession>)session;

#pragma mark --------- 跟踪拖动移动 ----------
//告诉你的委托被拖动的数据在集合视图上的位置发生了变化。//如果内容被放置在指定位置,如何处理该内容的建议。
//destinationIndexPath 将要删除内容的索引路径。

//当用户拖放内容时,集合视图会反复调用此方法,
//以确定如果拖放发生在指定位置,您将如何处理。集合视图根据您的建议向用户提供可视反馈。
//在这个方法的实现中,创建一个UICollectionViewDropProposal对象,
//并用它来传达你的意图。因为当用户在表视图上拖拽时,这个方法会被反复调用,
//所以您的实现应该尽可能快地返回。
- (UICollectionViewDropProposal *)collectionView:(UICollectionView *)collectionView
   dropSessionDidUpdate:(id<UIDropSession>)session 
    withDestinationIndexPath:(nullable NSIndexPath *)destinationIndexPath;

//当拖动的内容进入集合视图的边界矩形时通知你。
//当被拖动的内容进入其边界矩形时,集合视图调用此方法。
//直到拖动的内容退出集合视图的边界(触发
//对collectionView:dropSessionDidExit:方法的调用)并再次进入,
//才会再次调用该方法。
//使用此方法执行与跟踪集合视图上拖动的内容相关的任何一次性设置。
- (void)collectionView:(UICollectionView *)collectionView  
    dropSessionDidEnter:(id<UIDropSession>)session;

//当拖动的内容退出集合视图的边界矩形时通知你。
//当被拖动的内容退出指定集合视图的边界矩形时,
//UIKit调用这个方法。直到拖动的内容进入集合视图的边界(触发对
//collectionView:dropSessionDidEnter:方法的调用)并再次退出,
//才会再次调用该方法。使用此方法可以清除在
//collectionView:dropSessionDidEnter:方法中配置的任何状态信息。
- (void)collectionView:(UICollectionView *)collectionView 
   dropSessionDidExit:(id<UIDropSession>)session;

//当拖动操作结束时通知您。
//集合视图在某一点上的拖动结束时调用此方法。
//使用它可以清除 用于处理 拖动的任何状态信息。
//无论数据是否已实际放到集合视图中,都会调用此方法。
- (void)collectionView:(UICollectionView *)collectionView 
   dropSessionDidEnd:(id<UIDropSession>)session;

#pragma mark --------- 提供自定义预览 ------------
//返回有关如何在拖放过程中 在指定位置 显示项目的自定义信息。
//使用此方法可以在放置过程中自定义项目的外观。
//如果没有实现此方法,或者实现了它并返回nil,
//则集合视图将使用单元格的可见边界来创建预览。
//注意:
//允许自定义用于要删除的项目的预览。
//如果未实现或返回nil,则整个单元格将用于预览。
//当通过[UICollectionViewDropCoordinator dropItem:toItemAtIndexPath:]
//设置放置动画时,将根据需要 调用此项(若要自定义占位符放置,
//请参阅 UICollectionViewDropPlaceholder.previewParametersProvider)
- (nullable UIDragPreviewParameters *)collectionView:(UICollectionView *)collectionView 
  dropPreviewParametersForItemAtIndexPath:(NSIndexPath *)indexPath;

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容