iOS
UIKit
Swift

[Swift] CollectionViewは、簡単にドラッグ&ドロップで移動できる?

More than 1 year has passed since last update.

はじめに

iOS9以上のみのサポートですが、
CollectionViewCellを簡単に移動できるメソッドがあることを最近知りましたので、
共有します。(遅)

動作イメージ

output.gif

サンプルコード

まずは、サンプルコードを御覧ください。
CollectionViewの実装自体は、特別なことをしていません。
ロングタップのジェスチャー部分に注目してください。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    private var numbers = [Int]()

    override func viewDidLoad() {
        super.viewDidLoad()

        numbers = loadTestData()
        addEventListner()
    }

    //ダミーデータです
    private func loadTestData() -> [Int]{

        for i in 1...100 {
            numbers.append(i)
        }
        return numbers
    }

    private func addEventListner() {
        let longPressGesture = UILongPressGestureRecognizer(target: self,
                                                            action: #selector(self.handleLongGesture(_:)))
        collectionView.addGestureRecognizer(longPressGesture)
    }

    //ここがポイントです
    func handleLongGesture(gesture: UILongPressGestureRecognizer) {

        switch(gesture.state) {

        case UIGestureRecognizerState.Began:
            guard let selectedIndexPath = collectionView.indexPathForItemAtPoint(gesture.locationInView(collectionView)) else {
                break
            }
            collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)

        case UIGestureRecognizerState.Changed:
            collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))

        case UIGestureRecognizerState.Ended:
            collectionView.endInteractiveMovement()

        default:
            collectionView.cancelInteractiveMovement()
        }
    }
}

extension ViewController: UICollectionViewDataSource {

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numbers.count
    }

    func collectionView(collectionView: UICollectionView,
                        cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(LabelCollectionViewCell.reuseIdentifier,
                                                                         forIndexPath: indexPath) as! LabelCollectionViewCell
        cell.number = numbers[indexPath.item]
        return cell
    }

    func collectionView(collectionView: UICollectionView,
                        moveItemAtIndexPath sourceIndexPath: NSIndexPath,
                        toIndexPath destinationIndexPath: NSIndexPath) {

        let tempNumber = numbers.removeAtIndex(sourceIndexPath.item)
        numbers.insert(tempNumber, atIndex: destinationIndexPath.item)
    }
}

UILabelだけのCollectionViewCellです。

LabelCollectionViewCell.swift
import UIKit

protocol ReusableView: class {}

extension ReusableView where Self: UIView {
    static var reuseIdentifier: String {
        return String(self)
    }
}

class LabelCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var numberLabel: UILabel!
    var number = 0 {

        didSet {
            numberLabel.text = "\(number)"
        }
    }
}

extension LabelCollectionViewCell: ReusableView {}


ちょっと解説

ドラッグ&ドロップの機能は、ロングタップのジェスチャーと連動して、
下記のメソッドを呼ぶだけです。

メソッド名 説明
beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) 移動開始
updateInteractiveMovementTargetPosition(targetPosition: CGPoint) 移動中
endInteractiveMovement() 移動終了
cancelInteractiveMovement() 移動の取り消し

まとめ

iOS10の正式リリース間近ですが、
iOS9でも知らない機能がまだまだあるようです。

iOS8までは、ドラッグ&ドロップの実装は大変でしたが、
iOS9では簡単にできるようになっていたようです。(遅 2回目)

iPhone標準カレンダーのような実装ができるように鍛錬したいと思います。