初步实现了一个仿今日头条的频道管理,可以进行拖拽排序,效果图以下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/...