SSCollectionViewExchangeController 0.1.1

SSCollectionViewExchangeController 0.1.1

测试已测试
Lang语言 Obj-CObjective C
许可 MIT
Released上次发布2014年12月

未声明者 维护.



  • Murray Sagal

SSCollectionViewExchangeController 管理在集合视图中交换 2 个项目的过程。其关键词是 交换,与更常见的移动场景相比,结果略有不同。考虑以下 5 个项目的示例。

item1    item2    item3    item4    item5

在移动场景中,当项目 1 移动到项目 5 时,这是结果:

item2    item3    item4    item5    item1

在移动过程中,从 from 项到 to 项之间的所有项目都重新定位到 from 项的原始位置。

在交换场景中,当项目 1 与项目 5 交换时,这是结果:

item5    item2    item3    item4    item1

在交换中,to 项和 from 项之间的项目不会移动。只有 from 项和 to 项会移动。

功能

  • 完整的协议可让代理了解并掌握。
  • 支持无法移动或交换的项目。
  • 在过程开始和结束时使用默认动画,或实现您自己的动画。
  • 安全处理中断,如在中途接电话。

概念性对象模型

    ---------------------
    |                   |       delegate
 ---|  ViewController   |<----------------------
|   |                   |                       |
|   ---------------------                       |
|        |                           ------------------------------------------
|        |  @property (strong, ...   |                                        |
|         -------------------------->|   SSCollectionViewExchangeController   |
|                                    |                                        |
|                                    ------------------------------------------
|                                         |                            |
|                                         V                            |
|                              ----------------------------------      |
|                              |                                |      |
|                      ------->|  UILongPressGestureRecognizer  |      |
|                     |        |                                |      |
|                     |        ----------------------------------      |
|   ------------------------                                           V
|   |                      |            ---------------------------------------
 -->|   UICollectionView   |----------> |                                     |
    |                      |            |   SSCollectionViewExchangeLayout    |
    ------------------------            |                                     |
                                        ---------------------------------------

初始化 SSCollectionViewExchangeController 的实例时,它会为您集合视图安装一个手势识别器,并创建一个自定义布局对象,即 SSCollectionViewExchangeLayout,它是一个 UICollectionViewFlowLayout 的子类,并设置在您的集合视图上。布局管理交换过程中集合视图中的项目显示。通过 SSCollectionViewExchangeControllerDelegate 协议,您的视图控制器始终被通知,允许您在集合视图上发生更改时保持您的模型同步,并在过程中执行任何所需的实时更新。您的视图控制器必须保留对交换控制器的强引用。

如果您的视图包含多个集合视图,您可以拥有每个集合视图的交换控制器。但不能在集合视图之间进行交换。

术语

捕获:捕获是交换过程的开始。用户通过在集合视图单元格上长按来启动此过程。如果手势被识别,捕获动画运行(默认或您的自定义动画)来向用户表明捕获成功。

释放:用户放手指,通常是另一个项目上方,结束过程。释放动画运行(再次,默认或您的自定义动画)。

交换交易:交换交易从抓取开始,到释放结束,通常是在另一个项目上进行,导致两个项目交换。然而,在抓取和释放之间,用户可能拖动多个项目,包括,可能的话,起始位置的项目。

交换事件:在交换交易期间,每次用户将拖动到不同的项目时都会发生交换事件。单个交换交易中可以包含多个交换事件。交换事件包含一个或两个单个交换。如果是交易中的第一个交换事件,则在被拖动的项目和被拖动的目标项目之间有一个单个交换。如果用户继续拖动到新项目,后续的交换事件将包含两个单个交换:一个是撤销上一个交换,另一个是新的交换。请参阅下面的时间线图。

位移项:当用户拖动到新项目时,该项目会被位移。它动画地回到被拖动的项目的原始位置。同时,之前被位移的项目会动画地回到其原始位置。为了指示位移项,布局会降低其不透明度。

隐藏项:在抓取和释放之间,被拖动项目的单元格被隐藏。这是由布局管理的。尽管如此,隐藏项在交换交易进行时仍跟随用户。如果您好奇,请从 indexPathForItemToHide 返回 nil,您将能够观察这一点,最好在带有慢速动画的模拟器中查看。

快照:在抓取期间,单元格的快照被创建。快照在长按期间跟随用户的指尖。

抓取矩形:在某些实现中,集合视图的单元格只有在长按发生在单元格内的特定矩形上时才能被抓取。这就是抓取矩形。请参阅可选的 exchangeController:viewForCatchRectangleForItemAtIndexPath: 代理方法。

交换交易和交换事件:时间线视图

                                        Exchange Transaction
|--------------------------------------------------------------------------------------------->|
Catch at index path 0,3                                                Release at index path 1,5


        Exchange Event 1                Exchange Event 2               Exchange Event 3
|----------------------------->||----------------------------->||----------------------------->|
move from:   0,3 to 1,3                    1,3 to 1,4                      1,4 to 1,5


         new exchange            undo previous   new exchange   undo previous    new exchange
|----------------------------->||------------->||------------>||-------------->||------------->|
exchange:   0,3 with 1,3          1,3 with 0,3    0,3 with 1,4   1,4 with 0,3     0,3 and 1,5

在时间线中,您可以看到一个交换交易可以包括多个交换事件。交换事件有一个或两个单个交换。如果有两个,第一个是用来撤销之前的交换。如果只有一个,则没有之前的交换可以撤销。

演示

您可以在 这里 查看演示应用程序 SSCollectionViewExchangeController

安装

SSCollectionViewExchangeController 使用 UICollectionView,从 iOS 6.0 开始可用。

源文件

或者,将这些 8 个文件复制到您的 Xcode 项目中

  • SSCollectionViewExchangeController.h 和 .m
  • SSCollectionViewExchangeLayout.h 和 .m
  • UIView+SSCollectionViewExchangeControllerAdditions.h 和 .m
  • NSMutableArray+SSCollectionViewExchangeControllerAdditions.h 和 .m

使用方法

  1. 在您的视图控制器中导入 SSCollectionViewExchangeController.h...

    #import "SSCollectionViewExchangeController.h"
    
  2. 采用 SSCollectionViewExchangeControllerDelegate 协议...

    @interface MyViewController () <SSCollectionViewExchangeControllerDelegate>
    
  3. 为交换控制器创建一个属性...

    @property (strong, nonatomic) SSCollectionViewExchangeController *exchangeController;
    
  4. viewDidLoad 中创建 SSCollectionViewExchangeController 的实例...

    self.exchangeController = [[SSCollectionViewExchangeController alloc] initWithDelegate:self
                                                                            collectionView:self.collectionView];
    
  5. 获取布局并按需配置...

    UICollectionViewFlowLayout *layout = self.exchangeController.layout;
    layout.itemSize = CGSizeMake(150, 30);
    ...
    
  6. 实现下一节中描述的所需协议方法。

  7. 可选。本示例应用包含一个关于 NSMutableArray 的类别,它实现了交换两个可能位于不同数组中的项目的功能。您可以导入该类别,并在您的 exchangeController:didExchangeItemAtIndexPath1:withItemAtIndexPath2: 代理方法实现中使用该方法。请参考演示应用中的 ViewController.m 文件以获取示例实现。

    [NSMutableArray exchangeObjectInArray:array      atIndex:indexPath1.item
                   withObjectInOtherArray:otherArray atIndex:indexPath2.item];
    

    注意:如果您的集合视图有多个部分,且每个部分都有一个数组,请参考 ViewController.m 中的 arrayForSection: 方法以了解如何将集合视图的部分映射到数组。您可能需要实现类似的内容。

  8. 可选。交换控制器在交换过程中提供了默认动画来向用户提供反馈。一些与这些动画相关的属性被暴露出来,以便您可以根据需要配置它们。请参考以下属性声明中的注释。

  9. 可选。如果您认为公开的属性未能提供足够的控制,则可以实现以下可选代理方法...

    • 创建快照
    • 动画捕获
    • 动画释放

必需的代理方法

- (void)   exchangeController:(SSCollectionViewExchangeController *)exchangeController
  didExchangeItemAtIndexPath1:(NSIndexPath *)indexPath1
         withItemAtIndexPath2:(NSIndexPath *)indexPath2;

在交换事件中调用每个单个交换。每个事件可能有一个或两个交换。无论哪种情况,代理都应该只通过在指示的索引路径处交换元素来更新模型。请参考上述交换事件描述和“交换事务和交换事件:时间线视图”。此方法为代理提供了与视图上发生的变化同步其模型的机会。如果您正在执行任何类型的实时更新(如用户拖动),则通常不在此调用此方法,因为这个方法对于每个交换事件可能会被调用两次。实时更新应在 exchangeControllerDidFinishExchangeEvent: 中实现。


- (void)exchangeControllerDidFinishExchangeEvent:(SSCollectionViewExchangeController *)exchangeController;

当交换事务完成(用户抬起手指)时调用。此方法为代理提供了用户拖动时执行实时更新的机会。


- (void)exchangeControllerDidFinishExchangeTransaction:(SSCollectionViewExchangeController *)exchangeController
                                        withIndexPath1:(NSIndexPath *)indexPath1
                                            indexPath2:(NSIndexPath *)indexPath2;

当交换事务完成时调用(用户抬起手指)。索引路径代表最终交换的两个项目。不要交换这些项目,因为您已经交换了。此方法允许代理在事务结束时执行所需的任何任务,例如设置撤销。如果用户将手指拖回起始位置并释放(实际上没有交换),则索引路径将相同。


- (void)exchangeControllerDidCancelExchangeTransaction:(SSCollectionViewExchangeController *)exchangeController;

当长按手势识别器的状态变为 UIGestureRecognizerStateCancelled 时调用。通常,这仅在交换事务期间设备接收到电话时发生。当发生这种情况时,交换控制器会帮助代理返回到交换事务开始之前的状态。为此,交换控制器首先在代理上调用 exchangeController:didExchangeItemAtIndexPath1:withItemAtIndexPath2: 并传递最后交换项目的索引路径,以便代理可以恢复模型。然后调用此方法,使代理执行所需的任何其他操作。通常,代理不需要在对集合视图采取任何行动。交换控制器将集合视图返回到其以前的状态。因为没有应用动画,所以视图是隐藏的。

可选代理方法

- (BOOL)exchangeControllerCanBeginExchangeTransaction:(SSCollectionViewExchangeController *)exchangeController
                                  withItemAtIndexPath:(NSIndexPath *)indexPath;

如果实现,则在开始交换事务之前调用以确定是否可以开始。如果具有以下要求的任何或全部要求,请实现此方法

  1. 代理需要知道何时开始交换事务以便准备(更新其UI,关闭其他手势等)。如果您返回 YES,则可以安全地假设交换事务将开始。
  2. 并且/或者委托条件性地允许交换。例如,可能只允许在编辑时进行交换。
  3. 并且/或者集合视图中的一些项不能移动。位于 indexPath 的项是要移动的项。重要提示:是否可以移动项在这里确定。是否可以替换项在 canDisplaceItemAtIndexPath: 方法中确定。如果项既不能移动也不能替换,则需要实现这两个方法。

如果不想开始此交换事务,则返回 NO。如果返回 YES,则可以安全地假设交换事务将开始。如果没有实现,交换控制器假设为 YES。


- (BOOL)          exchangeController:(SSCollectionViewExchangeController *)exchangeController
          canDisplaceItemAtIndexPath:(NSIndexPath *)indexPathOfItemToDisplace
   withItemBeingDraggedFromIndexPath:(NSIndexPath *)indexPathOfItemBeingDragged;

如果实现,则在整个交换事务中调用,以确定是否可以交换这两个项。如果你的集合视图中包含完全不能交换的项,或者可能存在无法与被拖动的特定项交换的被替换项的情况,则需要实现此方法。如果没有实现,则默认为 YES。


- (UIView *)           exchangeController:(SSCollectionViewExchangeController *)exchangeController
  viewForCatchRectangleForItemAtIndexPath:(NSIndexPath *)indexPath;

如果您的集合视图中只可以在长按发生在一个特定矩形上时捕获单元格,则实现此方法并返回表示该矩形的视图。如果此方法没有实现,则假定捕获矩形为整个单元格。


- (UIView *)exchangeController:(SSCollectionViewExchangeController *)exchangeController
               snapshotForCell:(UICollectionViewCell *)cell;

SSCollectionViewExchangeController 实现了一个默认方法来使用默认背景色和透明度创建单元格快照。如果这不满足您的要求,则实现此委托方法。在实现此方法之前,请记住,默认快照方法中使用的背景颜色和透明度属性是公开的。考虑在实现此方法之前设置这些属性。


- (void)animateCatchForExchangeController:(SSCollectionViewExchangeController *)exchangeController
                             withSnapshot:(UIView *)snapshot;

- (void)animateReleaseForExchangeController:(SSCollectionViewExchangeController *)exchangeController
                               withSnapshot:(UIView *)snapshot                              // this is the view the user has been dragging
                                    toPoint:(CGPoint)centerOfCell                           // this is the center of the cell where the release occurred
            originalIndexPathForDraggedItem:(NSIndexPath *)originalIndexPathForDraggedItem  // animate the alpha for the cell at this index path back to 1.0
                            completionBlock:(PostReleaseCompletionBlock)completionBlock;    // you must execute this completion block in your final completion block

为了向用户提供反馈,SSCollectionViewExchangeController 实现了捕获和释放的默认动画。如果这些实现不满足您的要求,则实现其中一个或两个委托方法。

如果您实现了 animateReleaseForExchangeController 方法,则应执行以下操作

  1. snapshot 渲染到 centerOfCell
  2. originalIndexPathForDraggedItem 中单元格的 alpha 渲染回 1.0。
  3. 不要调用 invalidateLayout 或从其父视图中移除快照。
  4. 在最终的完成块中,执行 completionBlock 并传递一个动画持续时间。 completionBlock 管理交换事务最后时刻的顺序。首先,它设置一些内部状态,然后调用 invalidateLayout,这样就会取消隐藏隐藏的单元格(用户将其拖动到的地方)。取消隐藏立即发生,没有任何动画。但是,故意的,用户拖动的快照仍然在视图上,所以单元格的立即取消隐藏发生在快照的后面,所以没有变化可见。然后 completionBlock 根据您提供的持续时间将快照的透明度渲染为 0.0,根据您提供的持续时间,揭示现在取消隐藏的单元格。当这个动画完成后,它会从集合视图中移除快照。
    ...
    } completion:^(BOOL finished) {
        completionBlock(duration);
    }];

公开方法

- (instancetype)initWithDelegate:(id<SSCollectionViewExchangeControllerDelegate>)delegate
                  collectionView:(UICollectionView *)collectionView;

这是指定的初始化器。 delegate,通常是您的视图控制器,必须遵守 SSCollectionViewExchangeControllerDelegate 协议。collectionView 是交换控制器将管理的集合视图。


- (UICollectionViewFlowLayout *)layout;

此方法提供方便。委托可以要求其集合视图获取其布局,但这将返回一个 UICollectionViewLayout,在配置之前需要将其转换为 UICollectionViewFlowLayout。此方法方便地将布局作为 UICollectionViewFlowLayout 返回,准备好配置。

公开属性

@property (weak, nonatomic, readonly)   UILongPressGestureRecognizer    *longPressGestureRecognizer; 
// exposed to allow the delegate to set its properties as required
// by default minimumPressDuration is 0.15 and delaysTouchesBegan is YES

@property (nonatomic) CGFloat           alphaForDisplacedItem;  
// to distinguish the displaced item, default: 0.60

@property (nonatomic) NSTimeInterval    animationDuration;          
// the duration of each segment of the default animations, default: 0.20

@property (nonatomic) CGFloat           blinkToScaleForCatch;       
// default: 1.20

@property (nonatomic) CGFloat           blinkToScaleForRelease;     
// default: 1.05

@property (nonatomic) CGFloat           snapshotAlpha;              
// default: 0.80

@property (strong, nonatomic) UIColor   *snapshotBackgroundColor;   
// if you set to nil, no background color will be applied, default: [UIColor darkGrayColor]

@property (nonatomic, readonly) BOOL    exchangeTransactionInProgress; 
// allows clients to determine if there is an exchange transaction in progress

@property (nonatomic) double            animationBacklogDelay;
// When the long press is cancelled, for example by an incoming call, depending on the 
// velocity there may be move animations in progress and pending. Without a delay, the 
// backlog of animations can still be executing when the exchange controller calls 
// reloadData. This prevents reloadData from working properly and restoring the 
// collection view to its previous state. The delay allows the backlog of animations 
// to complete before the exchange controller cancels the exchange. The default is 0.50
// and should be sufficient in most cases but is exposed in case that doesn't meet 
// your requirements.

限制

  • 如果你的视图中包含多个集合视图,交换控制器不支持在集合视图之间进行交换。
  • 不支持滚动。所有可以交换的单元格必须可见在屏幕上。
  • 交换控制器不提供对旋转的直接支持。但如果你的视图控制器允许旋转并且按需管理布局,交换控制器应该仍然可以工作(需要测试)。但是旋转事件不应在交换事务期间发生。你的视图控制器可以询问交换控制器是否正在进行交换事务。

生产示例

PaddlesUp! Coach 使用 SSCollectionViewExchangeController 来帮助实现其龙舟队阵容编辑器。你可以在这里看到它的实际应用 这里

感谢...

  • Bolot Kerimbaev:在 iOS 6 发布时告诉我关于集合视图。
  • Matt Galloway:花了时间回答我的问题,“我可以用集合视图做这个吗?”
  • Tony Copping:在后台线程方面对我进行了指导。
  • Cesare Rocchi:耐心地忍受了多次代码审查并提供出色的建议。
  • Gijs van Klooster:问我,“为什么那些方法那么长?” 以及那个出色的枚举建议。

灵感来源于...

LXReorderableCollectionViewFlowLayout:https://github.com/lxcid/LXReorderableCollectionViewFlowLayout

关于演示应用程序

你可以克隆或下载仓库以使用演示应用程序。它锻炼了一些但并非所有 SSCollectionViewExchangeController 的功能。

测试

演示应用程序包含了一组针对 exchangeObjectInArray:atIndex:withObjectInOtherArray:atIndex: 方法的测试。如果你将测试文件复制到项目中,你可能需要配置项目以便测试目标可以识别文件。

  1. 在项目导航器面板中选中你的项目。
  2. 在项目/目标面板中选中你的项目。
  3. 你必须处于“信息”选项卡。
  4. 在“配置”中展开“调试”和你的项目。
  5. 在“测试”中从弹出菜单中选择“Pods”。