我正在参加「启航计划」

作用图

这个是前段时间项目新增的一个功用,刚刚开端组员是用UIScrollView + UIView 完结的,但这种完结办法属实是有点low,后续闲暇时笔者用UICollectionView简略完结了下。

思路

简略理一下思路,首先是把整个页面先布局出来,这儿涉及到一个UICollectionViewSection背景色的问题,有需求的能够点这儿,有具体的介绍。移动动画也很简略,先获取起点cell和终点cell,再新建一个动画的AnimationItem,依据获取的起点和终点cell动画就行,最后再完结拖拽排序作用。

大致总结一下:

  • 布局
  • 移动动画
  • 拖拽排序

下面就依据思路一步步来。我们必定要有一个认识,不论是多么杂乱的动画,只需把它分化开来,按过程一步一步完结就很简略。

完结

我们就跟上面的思路一步一步完结。

布局

首先想到必定是新建一个Model来办理这个数据,新建Model也要有点技巧。

struct ItemModel {
  var section: Int = -1          // cell的section索引
  var item: Int = -1             // cell的item索引
  var name: String = ""          // 名称
  var isAdded: Bool = false      // 是否增加到首页运用(第0区)
  var id: String {               // 唯一标识,能够用这个来命名图片的名称,也能够用来作判别 
    get {
      "\(section)_\(item)"
    }
  }
  init(){}
}

看得出来,ItemModel的特点section + item = IndexPath,能够依据 model 知道当时cell的所在方位了。

笔者这用的是 struct ,感觉用 class 会更好点,由于后续会改变数组中Model的特点值。现已写了就懒得再改了。

存在2个数组数据:

  • var editItems = [ItemModel](),由前一页传入的、可修改、拖拽的数据,坐落UICollectionView的第0个Section。
  • var datas = [[ItemModel]](),按照Section的次序,寄存所有的数据。

注意:Section要从1开端,由于第0个Section是能够修改拖拽的区域。

datas中寄存悉数的数据:

for i in 0..<names.count {
    let subNames = names[i]
    var items = [ItemModel]()
    for j in 0..<subNames.count {
        var model = ItemModel()
        model.section = i+1    // 注意这儿的Section要从1开端
        model.item = j
        model.name = subNames[j]
        model.isAdded = editItems.contains(where: { $0.id == model.id})
        items.append(model)
     }
     datas.append(items)
 }

依据数据布局UICollectionView

移动动画

移动只需2个操作,增加运用和删去运用。

增加

笔者这儿规定了最多能够增加8个运用。

大致思路:

  • 获取当时点击的 cell,为了得到其坐标作为动画开端方位
  • 在 collectionView 中刺进一个空白的 cell 占位,此举是为了增加或削减行数的动画过渡更自然;对应也应该在 editItems 中增加一个空白的 model 作为数据源,等移动动画完毕后再给model从头赋值。
  • 获取新刺进的空白 cell,为了得到其坐标作为动画的完毕方位
  • 生成动画的 cell,开端 -> 完毕 动画。
  • 更新数据,改写

删去

思路与增加雷同,且比之更简略

具体的思路和过程,代码中都有一步步的注释,可自行查阅。

拖拽排序

这个拖拽排序,在iOS11之前的比较麻烦,都是靠自己计算,这儿也简略说下思路:

iOS11.0之前的完结思路

  1. 在UICollectionView上增加一个长按的手势
  2. 在UICollectionView上面增加一个浮动隐藏的cell,便于拖拽
  3. 经过长按操作找到需求被拖动的cellA
  4. 经过拖动cellA找到找到和它交流方位的cellB
  5. 交流cellA和cellB的方位
  6. 替换数据源,把开端方位的数据模型删去,然后将开端方位的数据模型刺进到拖拽方位

这种比较杂乱的是结合方位判别需求交流的cell。但是在iOS11之后,UICollectionView新增了dragDelegatedropDelegate,用来完结拖拽排序的作用。

dragDelegate、dropDelegate

直接上代码:

collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .immediate
collectionView.isSpringLoaded = true
  • dragInteractionEnabled 特点要设置为 true,才能够进行 drag 操作。此特点在 iPad 默许是 true,在 iPhone 默许是 false。
  • reorderingCadence 重排序节奏,能够调节集合视图重排序的响应性。
    • UICollectionViewReorderingCadenceImmediate 默许值。当开端移动的时分就当即回流集合视图布局,实时的从头排序。
    • UICollectionViewReorderingCadenceFast 快速移动,不会当即从头布局,只要在中止移动的时分才会从头布局
    • UICollectionViewReorderingCadenceSlow 中止移动再过一会儿才会开端回流,从头布局
  • isSpringLoaded 弹性加载作用,也能够运用代理办法:func collectionView(_ collectionView: UICollectionView, shouldSpringLoadItemAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool

需求完结UICollectionViewDropDelegateUICollectionViewDragDelegate协议办法。下面是常用的几个办法,按照调用的先后次序说明一下:

/*
* 识别到拖动,一次拖动一个;若一次拖动多个,则需求选中多个
* 供给一个给定 indexPath 的可进行 drag 操作的 item
* NSItemProvider, 拖放处理时,带着数据的容器,经过方针初始化,该方针需满意 NSItemProviderWriting 协议
*/
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
/*
* 运用自定义预览,假如该办法没有完结或者返回nil,那么整个 cell 将用于预览
* UIDragPreviewParameters有2个特点:backgroundColor设置背景色彩;visiblePath设置视图的可见区域
* 笔者运用这个办法除去了拖拽过程中item的阴影
*/
func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters?
/*
* 开端拖拽后,持续增加拖拽的使命,处理雷同`itemsForBeginning`办法
*/
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
/*
* 拖拽开端,可自行处理
*/
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession)
/*
* 判别对应的 item 能否被执行drop会话,是否能放置
*/
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
/*
* 处理拖动中放置的战略,此办法会 频繁调用,在此办法中应尽可能削减工作量。
* 四种别离:move移动;copy复制;forbidden禁止,即不能放置;cancel用户撤销。
* 作用一般运用2种:.insertAtDestinationIndexPath 挤压移动;.insertIntoDestinationIndexPath 取代。
* 在某些情况下,方针索引途径可能为空(比如拖到一个没有cell的空白区域)你能够经过 session.locationInView 做你自己的命中测验
*/
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
/*
* 当drop会话进入到 collectionView 的坐标区域内就会调用
*/
func collectionView(_ collectionView: UICollectionView, dropSessionDidEnter session: UIDropSession)
/*
* 完毕放置时的处理
* 假如该办法不做任何事,将会执行默许的动画
*/
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
/*
* 拖拽开端,可自行处理
*/
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession)
/*
* 当dropSession 完结时会被调用,不论结果怎么。一般进行整理或改写操作
*/
func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession)

这儿大约的拖拽动画就差不多了,代码中会有更具体的注释。

总结

将一个大功用拆分成一个个小模块,按部就班一步一步完结就不难了。这儿只是中间囊括了各种小动画和改写,设计好思路,不行就多试几次必定能够。

代码自取:RCDragDropAnimation


若存在什么不对的地方,欢迎指正!