可拖动的UICollectionView

2,661 阅读2分钟

首先是实现效果

1、建立UICollectionViewCell

创建cell,其中只有一个UILabel,为其添加长按事件,并设置一个简单的抖动动画

import UIKit
import Foundation
import SnapKit

protocol CellItemLongPressProtocol:class {
    func cellItemLongPress(long:UILongPressGestureRecognizer)
}

class MyUIcollectionViewCell: UICollectionViewCell{
    var titleUILabel: UILabel!
    weak var delegate:CellItemLongPressProtocol?
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
        titleUILabel = UILabel(frame: frame)
        titleUILabel.textColor = UIColor.black
        titleUILabel.textAlignment = .center
        self.contentView.addSubview(titleUILabel)
        titleUILabel.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
        let longTap:UILongPressGestureRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(longClick(long:)))
        self.addGestureRecognizer(longTap)
    }
    
    @objc
    func longClick(long:UILongPressGestureRecognizer) {
        self.delegate?.cellItemLongPress(long: long)
         print("longClick")
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

//抖动动画
extension MyUIcollectionViewCell{
    func shakeAnimate() {
        let animate:CABasicAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
        animate.duration = 0.1 //周期
        animate.fromValue = -Double.pi/50
        animate.toValue = Double.pi/50
        animate.repeatCount = Float(LONG_MAX)
        animate.autoreverses = true //恢复原样
        self.layer.anchorPoint = CGPoint.init(x: 0.5, y: 0.5)
        self.layer.add(animate, forKey: "shake")
    }
    func resetAnimate(){
        self.layer.removeAnimation(forKey: "shake")
    }
}

2、其次是显示以及拖动控制部分

建立数据源,生成50组数据

  var resourceArray1:[[String:String]] = [[String:String]]()
  for i in 1...50 {
        var memu = [String:String]()
        memu["title"] = "第\(i)个哈哈哈"
        resourceArray1.append(memu)
    }

显示cell

func createMenu(){
        if uiCollectionView != nil{
            uiCollectionView?.removeFromSuperview()
        }
        //计算图标宽度
        width = UIScreen.main.bounds.width / (CGFloat(self.buttonColCount) + CGFloat(0.5))
        //计算菜单视图高度
        menuCollectionViewHeight = (width+10) * ceil(CGFloat(Float(resourceArray1.count) / Float(buttonColCount))) + CGFloat(40)
        // 初始化
        let layout = UICollectionViewFlowLayout.init()
        layout.itemSize = CGSize(width: width, height: width)
        
        layout.minimumLineSpacing = 10
        layout.minimumInteritemSpacing = 10
        layout.scrollDirection = .vertical
        
        // 设置分区头视图和尾视图宽高
        layout.headerReferenceSize = CGSize.init(width: UIScreen.main.bounds.width, height: CGFloat(40))
        uiCollectionView = UICollectionView.init(frame: CGRect(x:0, y:0, width:UIScreen.main.bounds.width, height:menuCollectionViewHeight), collectionViewLayout: layout)
        uiCollectionView?.backgroundColor = UIColor.white
        uiCollectionView?.delegate = self
        uiCollectionView?.dataSource = self
        uiCollectionView?.allowsSelection = true
        self.uiCollectionView.alwaysBounceVertical = true
        self.uiCollectionView.alwaysBounceHorizontal = false
        view.addSubview(uiCollectionView!)
        uiCollectionView!.snp.makeConstraints { (make) -> Void in
            make.edges.equalToSuperview()
        }
        // 注册cell
        uiCollectionView?.register(MyUIcollectionViewCell.self, forCellWithReuseIdentifier: "cell")
    }

填充数据

extension MyUICollectionViewViewController:UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return resourceArray1.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell:MyUIcollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyUIcollectionViewCell
        cell.titleUILabel.text = resourceArray1[indexPath.item]["title"]
        cell.titleUILabel.numberOfLines = 0
        cell.titleUILabel.lineBreakMode = .byCharWrapping
        cell.delegate = self
        return cell
    }
}

抖动动画

extension MyUICollectionViewViewController{
    func colllectionViewAnimateCell(isShake:Bool){
        for cell in uiCollectionView.visibleCells {
            let newcell = cell as! MyUIcollectionViewCell
            if isShake{
                newcell.shakeAnimate()
            }else{
                newcell.resetAnimate()
            }
        }
    }
}

长按拖动部分

//长按
extension MyUICollectionViewViewController:CellItemLongPressProtocol{
    func cellItemLongPress(long: UILongPressGestureRecognizer) {
        print("cellItemLongPress")
        let cell:MyUIcollectionViewCell = long.view as! MyUIcollectionViewCell
        if long.state == .began {//长按开始
            longPressedView = cell.snapshotView(afterScreenUpdates: true)
            longPressedView?.center = cell.center
            uiCollectionView.addSubview(longPressedView!)
            indexPath = uiCollectionView.indexPath(for: cell)!
            originCell = cell
            originCell.isHidden = true
            startPoint = long.location(in: uiCollectionView)
            colllectionViewAnimateCell(isShake: true)
        }else if long.state == .changed {
            //横线偏移量
            let tranx: CGFloat = long.location(ofTouch: 0, in: uiCollectionView).x - startPoint.x
            //纵向偏移量
            let trany:CGFloat = long.location(ofTouch: 0, in: uiCollectionView).y - startPoint.y
            //跟随手指移动
            longPressedView?.center = __CGPointApplyAffineTransform((longPressedView?.center)!, CGAffineTransform.init(translationX: tranx, y: trany))
            //更新当前移动结果
            startPoint = long.location(ofTouch: 0, in: uiCollectionView)
            //计算当前移动的cell和当前所处位置的cell位置,判断是否需要交换
            for cell in uiCollectionView.visibleCells {
                //跳过当前拖动的那个cell
                if uiCollectionView.indexPath(for: cell) == indexPath {
                    continue
                }
                //计算横线偏移量和纵向偏移量所对应的斜边的长度
                let space:CGFloat = sqrt(pow(longPressedView!.center.y - cell.center.y, 2) + pow(longPressedView!.center.x - cell.center.x, 2))
                if space <= longPressedView!.bounds.width * 0.5 && (abs(longPressedView!.center.x - cell.center.x) < longPressedView!.bounds.width * 0.5){
                    nextIndexPath = uiCollectionView.indexPath(for: cell)!
                    //如果是往后移动,先插入,再移除
                    if nextIndexPath.item > indexPath.item {
                        let menu = resourceArray1[indexPath.item]
                        resourceArray1.insert(menu, at: nextIndexPath.item)
                        resourceArray1.remove(at: indexPath.item)
                    }else {
                        //如果是往前移,先移除,再插入
                        let menu = resourceArray1[indexPath.item]
                        resourceArray1.remove(at: indexPath.item)
                        resourceArray1.insert(menu, at: nextIndexPath.item)
                    }
                    //移动
                    uiCollectionView.moveItem(at: indexPath, to: nextIndexPath)
                    indexPath = nextIndexPath
                    break
                }
            }
        }else if long.state == .ended{
            longPressedView?.removeFromSuperview()
            originCell.isHidden = false
            colllectionViewAnimateCell(isShake: false)
        }
    }
}