原文連接:UICollectionView 總結ios
項目源碼:模擬鳳凰新聞 Github 倉庫git
昨天給本身佈置這個做業以後,看完文檔實踐的過程當中發現一片很棒的英文總結,因而翻譯了一下。這篇總結會簡單總結一下我翻譯的那篇文章裏的內容,以及基於模擬鳳凰新聞客戶端部分頁面的一些 UICollectionView 使用總結。github
文章主要是總結一些須要注意的內容,具體請看源碼。實現的內容及對應文件包括:swift
同一個 section 內拖動 cell服務器
直接使用 UICollectionViewController(TestCollectionViewController.swift
)spa
在 UIViewController 中使用 UICollectionView(EditTabsViewController.swift
)翻譯
不一樣 section 間拖動 cell(Test.swift
)設計
不一樣 section 間點擊移動 cell(TabsViewController.swift
)code
點擊移除 cell(EditTabsViewController.swift
)繼承
主要內容如圖:
這篇文章主要介紹了在 iOS9 以後 UICollectionView 自帶的從新排列方法。
若是直接使用 UICollectionViewController,經過重寫`func collectionView(collectionView: UICollectionView,
moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) `便可以實現拖動重排。
若是是在 UIViewController 裏面使用 UICollectionView,則須要本身添加一個 UILongPressGestureRecognizer,對應狀態進行對應處理。
比較重要的幾個方法是:
func collectionView(collectionView: UICollectionView,
moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)
indexPathForItemAtPoint
beginInteractiveMovementForItemAtIndexPath
updateInteractiveMovementTargetPosition
endInteractiveMovement
cancelInteractiveMovement
特別注意
這個方法`func collectionView(collectionView: UICollectionView,
moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)`,
重寫這個方法以後,自帶的拖動重排才能生效。
這個方法究竟有什麼做用?這個方法是在 cell 位置變換以後觸發的。它包含兩個頗有用的參數,被拖動的 cell 的初始 indexPath 和落點 indexPath。由於這個位置的變換隻是視圖的改變,這些 cell 背後的數據的 index 其實並無受到影響。所以若是此時 reloadData() 會發現,格子位置又恢復了,但這不是咱們想要的,在實際項目中咱們但願移動後就一直保持那個位置,也就是說數據的 index 發生相應改變。這個方法就是方便咱們處理數據的。具體請看以後內容中的例子。
另外這個方法只與經過交互移動 cell 事件有關。若是是直接調用移動 cell 的方法並不會觸發這個方法。因此在相似鳳凰新聞編輯訂閱頻道頁面,」點擊下面 section 中的頻道,移動到上面的 section 中」,實現時須要在 didSelect 方法中添加對應修改數據源的代碼。具體參看源碼中TabsViewController.swift
。
首先你的 UIViewController 要繼承 UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout。
其次,記得綁定 delegate 和 datasource。
而後是:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
有須要的話用上:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView
特別注意
當你在 storyboard 設置了使用 header 或者 footer 或者二者都用的時候,記得添加對應的內容在 viewForSupplementaryElementOfKind 裏面。例如:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { if kind == UICollectionElementKindSectionHeader { let header = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "TabSectionHeader", forIndexPath: indexPath) as! TabSectionHeader return header } else { let footer = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "TabSectionFooter", forIndexPath: indexPath) return footer } }
我的對這個方法的設計持懷疑態度,以爲像 UITableView 那樣分離開會更好。(又或許是我理解不夠深入吧。)
記得設置 reuseIdentifier。
UICollectionView 是在生成 cell 的時候,先經過 numberOfItemsInSection 得到 cell 數量,而後一個一個生成添加在視圖中。
你可經過這些方法來插入、移動、刪除 cell:
insertItemAtIndexPaths
moveItemAtIndexPath
deleteItemsAtIndexPaths
好比,當來自服務器的數據更新了,新增或者減小了一個數據,咱們能夠想到有兩種狀況:
經過 reloadData() 將整個 UICollectionView 更新。
只在對應的位置插入或刪除對應的那一個 cell。
用第一種方法是沒有任何問題的。問題在第二種方法。
當咱們直接經過 insertItemAtIndexPaths 或者 deleteItemsAtIndexPaths 添加或刪除 cell 時,UICollectionView 中的 cell 數量發生變化了。貌似沒問題?若是你嘗試滑動一下屏幕,你會發現程序崩潰了。你會看到相似下面的報錯:
緣由在於,當 UICollectionView 進行任何的更新時,包括局部更新,都會檢查 numberOfItemInSection 方法返回的值和當前 UICollectionView 中實際包含的 cell 數量。若是兩者不一致就會報錯。
特別注意
UICollectionView 中實際包含的 cell 數量在下一次更新前 collection view 視圖前必定要和 numberOfItemInSection 的返回值一直。
因此咱們在新增或者刪除 cell 以後,記得要修改對應的數據源。(固然在實際項目中應該不會忘記。)
插入、移動、刪除 section 相似
項目中的 Test.swift 是關於不一樣 section 間拖動 cell 的例子。
基本原理和在一個 section 內拖動 cell 同樣,都是調用那幾個方法。
第一點
須要注意的仍是上面提到的記得修改對應數據源,不然第二次拖動就會報錯。由於此時兩個 section 內 cell 數量和 numberOfItemInSection 返回值不同了。
第二點
請看一下兩個實現方法:
一,『原始』方法
func longPressGestureRecognizerAction(sender: UILongPressGestureRecognizer) { switch sender.state { case .Began: let location = sender.locationInView(self.collectionView) let indexPath = self.collectionView.indexPathForItemAtPoint(location) self.originalSectionIndex = (indexPath?.section)! self.interactiveItem = self.collectionView.cellForItemAtIndexPath(indexPath!) self.collectionView.beginInteractiveMovementForItemAtIndexPath(indexPath!) break case .Changed: let location = sender.locationInView(self.collectionView) print(location) let indexPath = self.collectionView.indexPathForItemAtPoint(location) print(indexPath) self.collectionView.updateInteractiveMovementTargetPosition(location) case .Ended: self.collectionView.endInteractiveMovement() let currentSectionIndex = (self.collectionView.indexPathForCell(self.interactiveItem)?.section)! self.sections[currentSectionIndex]++ self.sections[self.originalSectionIndex]-- default: self.collectionView.cancelInteractiveMovement() break } }
二,藉助自帶方法的簡便方法
func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { self.sections[destinationIndexPath.section]++ self.sections[sourceIndexPath.section]-- } func longPressGestureRecognizerAction(sender: UILongPressGestureRecognizer) { switch sender.state { case .Began: guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(sender.locationInView(self.collectionView)) else { break } self.collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath) break case .Changed: self.collectionView.updateInteractiveMovementTargetPosition(sender.locationInView(self.collectionView)) break case .Ended: self.collectionView.endInteractiveMovement() default: self.collectionView.cancelInteractiveMovement() break } }
第一個方法定義了兩個全局變量var originalSectionIndex = 0
和var interactiveItem:UICollectionViewCell!
來記錄初始位置和正在進行移動的 cell。
而第二個方法,經過使用func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)
,直接就可使用開始和結束位置 indexPath。很是方便。
因此固然必定要用第二種方法。