Swift2.0下UICollectionViews拖拽效果的实现

文/过客又见过客(简书做者)
原文连接:http://www.jianshu.com/p/569c65b12c8b
著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。

原文UICollectionViews Now Have Easy Reordering。本着好东西要分享的原则以及出于对我的技能的提高,在此做一个粗陋的翻译,翻译尽可能保留原做内容。本文主要是基于Swift 2.0实现的一个简单的UICollectionView的cell的拖拽效果,编译环境是Xcode 7。效果虽然简单,但足够用不是吗? 对于翻译,本人也是第一次,不免有失误或错误之处,万望不吝赐教,以便及时修正。git

我是UICollectionView的忠实粉丝。相对于它的兄长UITableView,UICollectionView可定制性更高,且更加灵活。时至今日,我使用UICollectionView要远多于UITableView。随着IOS9的发布,使的它的排序(即拖拽效果)更加简单。在这以前,想要经过原生控件达到开箱即用的效果,那是一件不可能的事情,若是想要达到效果势必要完成大量的工做。首先让咱们从新回顾一下相关的API,而后你能够在经过Github下载示例Demogithub

实现简单的拖动排序效果最简单的办法就是使用UICollectionViewController。如今它有一个新增的属性installsStandardGestureForInteractiveMovement,经过添加手势对cell进行排序。这是一个BOOL类型的属性,默认为YES,而且咱们须要重写一个方法以来达到咱们想要的效果。swift

override func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { //调整数据源数据的顺序 }

当咱们重写了moveItemAtIndexPath,collectionView就认为cell是能够移动的。ide


1.gif

若是咱们使用带有collectionView的普通UIViewController实现拖拽效果,就会变得很复杂。咱们不只要实现UICollectionViewDataSource上面提到的的代理方法,还要重写installsStandardGestureForInteractiveMovement。可是不要担忧,实现起来一样简单。UILongPressGestureRecognizer长按手势,可以彻底知足拖拽需求。ui

private var longPressGesture: UILongPressGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() longPressGesture = UILongPressGestureRecognizer(target: self, action: "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() } }

当手势生效时,获取手势所在cell的indexPath,而后根据手势的不一样状态调用collectionView的相关方法,具体以下:spa

  • beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath)开始拖拽某个cell时调用此方法,并把将被拖拽的cell的indexPath传入方法。
  • updateInteractiveMovementTargetPosition(targetPosition: CGPoint)根据手势更新被拖拽的cell的位置
  • endInteractiveMovement()手势结束时调用,结束拖拽
  • cancelInteractiveMovement()手势取消时调用,取消拖拽

这样可以实现咱们想要的拖拽效果了。翻译


2.gif

使用普通UIViewController最终达到的效果跟咱们使用UICollectionViewController实现的效果是同样的。至关酷,不是吗?可是咱们能够经过自定义UICollectionViewLayout使它变得更酷。下面咱们来实现一个简单的瀑布流。代理


3.gif

啊哈,看起来至关酷,可是若是咱们不想在拖拽的过程当中改变cell的size,咱们应该怎么作呢?被拖拽的cell在移动的过程当中,应该保持size不变。这固然是能够实现的。UICollectionViewLayout为咱们提供了相关方法来解决这个问题。code

func invalidationContextForInteractivelyMovingItems(targetIndexPaths: [NSIndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [NSIndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext func invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths(indexPaths: [NSIndexPath], previousIndexPaths: [NSIndexPath], movementCancelled: Bool) -> UICollectionViewLayoutInvalidationContext

cell在起始indexPath和目标indexPath拖拽期间,会调用第一个方法。第二个方法相似,可是它仅会在拖拽结束后调用。根据这一点,咱们能够经过使用一个小技巧达到咱们的需求。blog

internal override func invalidationContextForInteractivelyMovingItems(targetIndexPaths: [NSIndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [NSIndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext { var context = super.invalidationContextForInteractivelyMovingItems(targetIndexPaths, withTargetPosition: targetPosition, previousIndexPaths: previousIndexPaths, previousPosition: previousPosition) self.delegate?.collectionView!(self.collectionView!, moveItemAtIndexPath: previousIndexPaths[0], toIndexPath: targetIndexPaths[0]) return context }

解决方法简单直接。获取当前被拖拽的cell的起始indexPath和目标indexPath,而后调用UICollectionViewDataSource代理方法移动当前正在被拖拽的cell。


4.gif

毫无疑问,一个能够拖拽的的collectionView会带来很是棒的体验效果。特别感谢UIKit工程师们的付出!

相关文章
相关标签/搜索