Swift 防頭條頻道管理(UICollectionView,拖拽排序)

前言

初步實現了一個仿今日頭條的頻道管理,可以進行拖拽排序,效果圖以下git

已上傳不了GIF圖片,你們點擊這個連接看效果圖吧https://github.com/LSnumber1/...github

分析

主要使用UICollectionView實現,利用其原生的API實現拖拽效果。數組

核心分爲如下步驟:session

  • 獲得獲取焦點的Cell
  • 處理移動中的事件
  • 移動結束後,處理放下Cell問題

建立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

以上是基本的操做流程,下面重點講下,上邊提到的三個步驟


獲得獲取焦點的Cell

須要實現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。

移動結束後,處理放下Cell問題

這是最後一步,須要實現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/...
相關文章
相關標籤/搜索