我是UICollectionView的忠實粉絲。這個類比起它的老哥UITableView類具有更高的可定製性。現在我用collection view的次數要比用table view還多。隨著iOS9的到來,它支援簡單的重排。在此之前,重排不可能有現成的方法,同時這樣做也是件痛苦的工作。現在讓我們來看看API,你可以在GitHub找到相應的Xcode工程。
新增簡單重排的最簡單的方式是用UICollectionViewController。它現在有了一個新的屬性叫installsStandardGestureForInteractiveMovement(為互動式移動工作設定標準手勢),這個屬性的新增使得我們可以用標準手勢來對cell單元進行重新排序。該屬性預設值為true,這意味著我們只需要過載一個方法就可以讓它正常工作。
1 2 3 4 5 |
override func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { // move your data order } |
Collection view推斷每個item(元素)可以被移動,因為moveItemAtIndexPath函式被過載了。
當我們想使用一個帶有collection view的簡單的UIViewController時,事情變得更加複雜。我們還需要實現之前提到的UICollectionViewDataSource的方法,但我們需要重寫installsStandardGestureForInteractiveMovement。別擔心,這些也很容易被支援。UILongPressGestureRecognizer是一個持續的、完全支援平移的手勢識別器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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() } } |
我們儲存了被選擇的索引路徑,這個路徑從longPressGesture handler(長按手勢處理器)中獲得,這個路徑還取決於它是否有任何我們允許的,跟平移手勢相關的值。接下來我們根據手勢狀態呼叫一些新的collection view方法:
- beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath)?開始在特定的索引路徑上對cell(單元)進行Interactive Movement(互動式移動工作)。
- updateInteractiveMovementTargetPosition(targetPosition: CGPoint)?在手勢作用期間更新互動移動的目標位置。】
- endInteractiveMovement()?在完成手勢動作後,結束互動式移動
- cancelInteractiveMovement()?取消Interactive Movement。
這讓處理平移手勢更容易理解了。
機器反應跟標準的UICollectionViewController一樣,真的很酷,但是還有更酷的–我們能對自定義的collection view layout(collection集合檢視佈局)申請重排,下面是在waterfall layout(瀑布佈局)裡對Interactive Movement的測試。
嗯哼,看起來很酷,但如果我們不想在移動cell(單元)的時候改變它們的大小,那該怎麼做?被選擇的cell(單元)的大小在Interactive Movement期間應該保持原樣。這是可行的。UICollectionViewLayout有附加的方法來處理重排。
1 2 3 4 5 6 7 8 |
func invalidationContextForInteractivelyMovingItems(targetIndexPaths: [NSIndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [NSIndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext func invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths(indexPaths: [NSIndexPath], previousIndexPaths: [NSIndexPath], movementCancelled: Bool) -> UICollectionViewLayoutInvalidationContext |
第一個函式在元素的Interactive Movement期間被呼叫,它帶有target(目標元素)和先前的cell的indexPaths(索引地址)。第二個與第一個函式類似,但它只在Interactive Movement結束後才呼叫。通過這些知識我們能通過一點小竅門,實現我們的需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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的之前的和目標索引路徑,然後呼叫UICollectionViewDataSource方法來移動這些item(元素)。
毫無疑問,collection view的重排是一個出色的附加功能,UIKit前端框架工程師幹得漂亮!:)
P.S:我要感謝Douglas Hill對於程式碼完善的一些建議。