前言
初步實(shí)現(xiàn)了一個(gè)仿今日頭條的頻道管理溶耘,能夠進(jìn)行拖拽排序,效果圖如下
分析
主要使用UICollectionView實(shí)現(xiàn)服鹅,利用其原生的API實(shí)現(xiàn)拖拽效果凳兵。
核心分為以下步驟:
得到獲取焦點(diǎn)的Cell
處理移動(dòng)中的事件
移動(dòng)結(jié)束后,處理放下Cell問題
創(chuàng)建UICollectionView之前先創(chuàng)建個(gè)UICollectionViewFlowLayout企软,我們定義一行最多顯示4個(gè)Cell庐扫,單個(gè)Cell之前的間距是10,高度為40
let width: CGFloat = (self.view.frame.width - 5 * 10) / 4
let height: CGFloat = 40
定義UICollectionViewFlowLayout
let flowLayout = UICollectionViewFlowLayout()
//滾動(dòng)方向
flowLayout.scrollDirection = .vertical
//網(wǎng)格中各行項(xiàng)目之間使用的最小間距
flowLayout.minimumLineSpacing = 10
//在同一行中的項(xiàng)目之間使用的最小間距
flowLayout.minimumInteritemSpacing = 10
//用于單元格的默認(rèn)大小
flowLayout.itemSize = CGSize.init(width: width, height: height)
//用于標(biāo)題的默認(rèn)大小
flowLayout.headerReferenceSize = CGSize.init(width: self.view.frame.width, height: 50)
headerReferenceSize 用于定義頭部的大姓躺凇(IndexPath.section對(duì)應(yīng)的部分)
創(chuàng)建UICollectionView形庭,UICollectionView需要register兩個(gè),一個(gè)是普通視圖的Cell藻治,一個(gè)是頭部對(duì)應(yīng)的Cell
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(如:“關(guān)注碘勉、特產(chǎn)、健康桩卵、房產(chǎn)等”)验靡,HeaderCollectionReusableView 為頭部的Cell (如:“我的頻道、頻道推薦”)
需要實(shí)現(xiàn)的代理
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.dragDelegate = self
myCollectionView.dropDelegate = self
delegate 管理集合視圖中項(xiàng)目顯示和選擇
dataSource 提供數(shù)據(jù)源
dragDelegate 管理拖曳交互
dropDelegate 管理丟棄交互
重點(diǎn)就是dragDelegate和dropDelegate
提供的數(shù)據(jù)源如下
func initData() {
itemHeaders = ["我的頻道","頻道推薦"]
itemNames = [0: [String](["關(guān)注","推薦","視頻","熱點(diǎn)","北京","新時(shí)代","圖片","頭條號(hào)","娛樂","問答","體育","科技","懂車帝","財(cái)經(jīng)","軍事","國際"]),1: [String](["健康","冬奧","特產(chǎn)","房產(chǎn)","小說","時(shí)尚","歷史","育兒","直播","搞笑","數(shù)碼","美食","養(yǎng)生","電影","手機(jī)","旅游","寵物","情感"])]
}
開啟UICollectionView響應(yīng)拖拽操作
myCollectionView.dragInteractionEnabled = true
以上是基本的操作流程雏节,下面重點(diǎn)講下胜嗓,上邊提到的三個(gè)步驟
得到獲取焦點(diǎn)的Cell
需要實(shí)現(xiàn)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]
}
由于我們規(guī)定钩乍,“我的頻道”可以拖拽辞州,“頻道推薦”不可以,所以 indexPath.section 為1 的部分寥粹,返回一個(gè)空數(shù)組变过,表示不響應(yīng)事件。
通過section和row 獲取到對(duì)象的值涝涤,并創(chuàng)建NSItemProvider返回媚狰。
NSItemProvider:拖放活動(dòng)期間在進(jìn)程之間共享的數(shù)據(jù)或文件,初始化的object要是NSObject, NSCopying的子類阔拳,如:NSItemProvider(object: item as NSString)崭孤,是把String作為共享數(shù)據(jù)了。
處理移動(dòng)中的事件
這個(gè)我們需要實(shí)現(xiàn)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)
}
}
由于“我的頻道”和“頻道推薦”是禁止互相滑動(dòng)的辨宠,所以,拖拽的起始dragingIndexPath和目標(biāo)destinationIndexPath的section不一樣货裹,就表示跨區(qū)了嗤形,設(shè)置其為forbidden。
移動(dòng)結(jié)束后弧圆,處理放下Cell問題
這是最后一步派殷,需要實(shí)現(xiàn)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 操作后拓轻,需要把之前的位置刪除掉,在新的位置進(jìn)行插入
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])
要先對(duì)數(shù)據(jù)源進(jìn)行移除和添加操作经伙,然后在視圖進(jìn)行更新扶叉,否則會(huì)崩潰
copy 操作后,需要在新的位置進(jìn)行插入
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)
以上就是全部的分析流程帕膜,還有一種實(shí)現(xiàn)思路是:在collectionView上添加一個(gè)自定義的View枣氧,覆蓋在Cell之上,獲取手勢(shì)事件后垮刹,根據(jù)手勢(shì)滑動(dòng)达吞,動(dòng)態(tài)更改自定義的View的位置,同樣可以實(shí)現(xiàn)以上效果荒典。