初步實現了一個仿今日頭條的頻道管理,可以進行拖拽排序,效果圖以下git
已上傳不了GIF圖片,你們點擊這個連接看效果圖吧https://github.com/LSnumber1/...github
主要使用UICollectionView實現,利用其原生的API實現拖拽效果。數組
核心分爲如下步驟:session
建立UICollectionView以前先建立個UICollectionViewFlowLayout,咱們定義一行最多顯示4個Cell,單個Cell以前的間距是10,高度爲40ide
let width: CGFloat = (self.view.frame.width - 5 * 10) / 4 let height: CGFloat = 40
定義UICollectionViewFlowLayout代理
let flowLayout = UICollectionViewFlowLayout() //滾動方向 flowLayout.scrollDirection = .vertical //網格中各行項目之間使用的最小間距 flowLayout.minimumLineSpacing = 10 //在同一行中的項目之間使用的最小間距 flowLayout.minimumInteritemSpacing = 10 //用於單元格的默認大小 flowLayout.itemSize = CGSize.init(width: width, height: height) //用於標題的默認大小 flowLayout.headerReferenceSize = CGSize.init(width: self.view.frame.width, height: 50)
headerReferenceSize 用於定義頭部的大小(IndexPath.section對應的部分)
建立UICollectionView,UICollectionView須要register兩個,一個是普通視圖的Cell,一個是頭部對應的Cellcode
myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) myCollectionView.register(UINib.init(nibName: "EditCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: EditCollectionViewCell.forCellReuseIdentifier) myCollectionView.register(UINib(nibName: "HeaderCollectionReusableView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.forCellReuseIdentifier)
EditCollectionViewCell 爲頻道的Cell(如:「關注、特產、健康、房產等」),HeaderCollectionReusableView 爲頭部的Cell (如:「個人頻道、頻道推薦」)
須要實現的代理orm
myCollectionView.delegate = self myCollectionView.dataSource = self myCollectionView.dragDelegate = self myCollectionView.dropDelegate = self
delegate 管理集合視圖中項目顯示和選擇
dataSource 提供數據源
dragDelegate 管理拖曳交互
dropDelegate 管理丟棄交互
重點就是dragDelegate和dropDelegate
提供的數據源以下視頻
func initData() { itemHeaders = ["個人頻道","頻道推薦"] itemNames = [0: [String](["關注","推薦","視頻","熱點","北京","新時代","圖片","頭條號","娛樂","問答","體育","科技","懂車帝","財經","軍事","國際"]),1: [String](["健康","冬奧","特產","房產","小說","時尚","歷史","育兒","直播","搞笑","數碼","美食","養生","電影","手機","旅遊","寵物","情感"])] }
開啓UICollectionView響應拖拽操做對象
myCollectionView.dragInteractionEnabled = true
以上是基本的操做流程,下面重點講下,上邊提到的三個步驟
須要實現UICollectionViewDragDelegate代理,並提供UIDragItem
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { guard indexPath.section != 1 else { return [] } let item = self.itemNames[indexPath.section]![indexPath.row] let itemProvider = NSItemProvider(object: item as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) dragItem.localObject = item dragingIndexPath = indexPath return [dragItem] }
因爲咱們規定,「個人頻道」能夠拖拽,「頻道推薦」不能夠,因此 indexPath.section 爲1 的部分,返回一個空數組,表示不響應事件。
經過section和row 獲取到對象的值,並建立NSItemProvider返回。
NSItemProvider:拖放活動期間在進程之間共享的數據或文件,初始化的object要是NSObject, NSCopying的子類,如:NSItemProvider(object: item as NSString),是把String做爲共享數據了。
這個咱們須要實現UICollectionViewDropDelegate代理,並提供UICollectionViewDropProposal
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { guard dragingIndexPath?.section == destinationIndexPath?.section else { return UICollectionViewDropProposal(operation: .forbidden) } if session.localDragSession != nil { if collectionView.hasActiveDrag { return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) } else { return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) } } else { return UICollectionViewDropProposal(operation: .forbidden) } }
因爲「個人頻道」和「頻道推薦」是禁止互相滑動的,因此,拖拽的起始dragingIndexPath和目標destinationIndexPath的section不同,就表示跨區了,設置其爲forbidden。
這是最後一步,須要實現UICollectionViewDropDelegate代理,經過coordinator咱們能夠獲取到操做類型,是move仍是copy。
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { guard let destinationIndexPath = coordinator.destinationIndexPath else { return } switch coordinator.proposal.operation { case .move: break case .copy: break default: return } }
move 操做後,須要把以前的位置刪除掉,在新的位置進行插入
self.itemNames[destinationIndexPath.section]!.remove(at: sourceIndexPath.row) self.itemNames[destinationIndexPath.section]!.insert(item.dragItem.localObject as! String, at: destinationIndexPath.row) collectionView.deleteItems(at: [sourceIndexPath]) collectionView.insertItems(at: [destinationIndexPath])
要先對數據源進行移除和添加操做,而後在視圖進行更新,不然會崩潰
copy 操做後,須要在新的位置進行插入
let indexPath = IndexPath(row: destinationIndexPath.row, section: destinationIndexPath.section) self.itemNames[destinationIndexPath.section]!.insert(item.dragItem.localObject as! String, at: indexPath.row) collectionView.insertItems(at: indexPath)
以上就是所有的分析流程,還有一種實現思路是:在collectionView上添加一個自定義的View,覆蓋在Cell之上,獲取手勢事件後,根據手勢滑動,動態更改自定義的View的位置,一樣能夠實現以上效果。
Git: https://github.com/LSnumber1/...