CollectionView中的自定义单元重新sorting行为

我能够重新排列我的collectionView像这样:

在这里输入图像说明

然而,并非所有的单元格水平移动,我只想交换以下行为(即用较less的单元格移动):

在这里输入图像说明

我一直在玩下面的代表方法:

func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath 

但是,我不确定如何实现自定义重新sorting行为。

我设法通过创buildUICollectionView的子类并将自定义处理添加到交互式移动来实现此UICollectionView 。 在查看如何解决您的问题的可能提示时,我发现这个教程: http : //nshint.io/blog/2015/07/16/uicollectionviews-now-have-easy-reordering/ 。 最重要的部分是交互式重新sorting不仅可以在UICollectionViewControllerUICollectionViewController 。 相关的代码如下所示:

 var longPressGesture : UILongPressGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() // rest of setup longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleLongGesture(_:))) self.collectionView?.addGestureRecognizer(longPressGesture) } func handleLongGesture(gesture: UILongPressGestureRecognizer) { switch(gesture.state) { case UIGestureRecognizerState.Began: guard let selectedIndexPath = self.collectionView?.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else { break } collectionView?.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath) case UIGestureRecognizerState.Changed: collectionView?.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!)) case UIGestureRecognizerState.Ended: collectionView?.endInteractiveMovement() default: collectionView?.cancelInteractiveMovement() } } 

这需要在您的视图控制器中放置您的集合视图。 我不知道这是否会与UICollectionViewController工作,可能需要一些额外的修补。 是什么导致我UICollectionView是认识到所有其他相关的类/委托方法只通知第一个和最后一个索引path(即源和目标),没有关于所有其他单元格重新排列的信息,所以它需要停止在核心。

SwappingCollectionView.swift

 import UIKit extension UIView { func snapshot() -> UIImage { UIGraphicsBeginImageContext(self.bounds.size) self.layer.renderInContext(UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } } extension CGPoint { func distanceToPoint(p:CGPoint) -> CGFloat { return sqrt(pow((px - x), 2) + pow((py - y), 2)) } } struct SwapDescription : Hashable { var firstItem : Int var secondItem : Int var hashValue: Int { get { return (firstItem * 10) + secondItem } } } func ==(lhs: SwapDescription, rhs: SwapDescription) -> Bool { return lhs.firstItem == rhs.firstItem && lhs.secondItem == rhs.secondItem } class SwappingCollectionView: UICollectionView { var interactiveIndexPath : NSIndexPath? var interactiveView : UIView? var interactiveCell : UICollectionViewCell? var swapSet : Set<SwapDescription> = Set() var previousPoint : CGPoint? static let distanceDelta:CGFloat = 2 // adjust as needed override func beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool { self.interactiveIndexPath = indexPath self.interactiveCell = self.cellForItemAtIndexPath(indexPath) self.interactiveView = UIImageView(image: self.interactiveCell?.snapshot()) self.interactiveView?.frame = self.interactiveCell!.frame self.addSubview(self.interactiveView!) self.bringSubviewToFront(self.interactiveView!) self.interactiveCell?.hidden = true return true } override func updateInteractiveMovementTargetPosition(targetPosition: CGPoint) { if (self.shouldSwap(targetPosition)) { if let hoverIndexPath = self.indexPathForItemAtPoint(targetPosition), let interactiveIndexPath = self.interactiveIndexPath { let swapDescription = SwapDescription(firstItem: interactiveIndexPath.item, secondItem: hoverIndexPath.item) if (!self.swapSet.contains(swapDescription)) { self.swapSet.insert(swapDescription) self.performBatchUpdates({ self.moveItemAtIndexPath(interactiveIndexPath, toIndexPath: hoverIndexPath) self.moveItemAtIndexPath(hoverIndexPath, toIndexPath: interactiveIndexPath) }, completion: {(finished) in self.swapSet.remove(swapDescription) self.dataSource?.collectionView(self, moveItemAtIndexPath: interactiveIndexPath, toIndexPath: hoverIndexPath) self.interactiveIndexPath = hoverIndexPath }) } } } self.interactiveView?.center = targetPosition self.previousPoint = targetPosition } override func endInteractiveMovement() { self.cleanup() } override func cancelInteractiveMovement() { self.cleanup() } func cleanup() { self.interactiveCell?.hidden = false self.interactiveView?.removeFromSuperview() self.interactiveView = nil self.interactiveCell = nil self.interactiveIndexPath = nil self.previousPoint = nil self.swapSet.removeAll() } func shouldSwap(newPoint: CGPoint) -> Bool { if let previousPoint = self.previousPoint { let distance = previousPoint.distanceToPoint(newPoint) return distance < SwappingCollectionView.distanceDelta } return false } } 

我意识到这里有很多事情要做,但我希望一切都会在一分钟内清楚。

  1. 使用帮助器方法扩展UIView以获取单元格的快照。
  2. CGPoint用辅助方法扩展来计算两点之间的距离。
  3. SwapDescription帮助器结构 – 它需要防止同一对项目的多个交换,导致animation的毛病。 它的hashValue方法可以被改进,但是为了这个概念certificate已经够好了。
  4. beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool – 移动开始时调用的方法。 一切都在这里设置。 我们得到我们的单元格的快照,并将其添加为子视图 – 这个快照将是用户在屏幕上实际拖动的内容。 单元格本身被隐藏起来。 如果您从此方法返回false ,交互式移动将不会开始。
  5. updateInteractiveMovementTargetPosition(targetPosition: CGPoint) – 每个用户移动后调用的方法,这是很多。 我们检查前一个点的距离是否足够小,以便交换项目 – 这样可以防止用户在屏幕上快速拖动时出现问题,并且多个项目会以非显而易见的结果进行交换。 如果交换可能发生,我们检查它是否已经发生,如果不是,我们交换两个项目。
  6. endInteractiveMovement()cancelInteractiveMovement()cleanup() – 移动结束后,我们需要将我们的帮助器恢复到默认状态。
  7. shouldSwap(newPoint: CGPoint) -> Bool – helper方法来检查运动是否足够小,以便我们可以交换单元格。

这是它的结果:

结果

让我知道这是你需要什么和/或如果你需要我澄清的东西。

这是一个演示项目 。