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工程師們的付出!

相關文章
相關標籤/搜索