Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionview - blink when move item

I want to reorder my cells in my UICollectionView. But when I drop my item, the "cellForItemAt" method is called and this will cause the cell to flash (See image below).

What should I do to avoid this behavior ?

Thank you in advance for your help.

enter image description here

 class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    private let cellIdentifier = "cell"
    private let cells = [""]

    private var longPressGesture: UILongPressGestureRecognizer!

    override func viewDidLoad() {
        super.viewDidLoad()

        longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:)))
        collectionView.addGestureRecognizer(longPressGesture)
    }

    //Selectors
    @objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
        switch(gesture.state) {

        case .began:
            guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
                break
            }
            collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
        case .changed:
            collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
        case .ended:
            collectionView.endInteractiveMovement()
        default:
            collectionView.cancelInteractiveMovement()
        }
    }

}

// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
        return true
    }

    func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 100, height: 100)
    }
}
like image 558
Louis Avatar asked May 07 '18 16:05

Louis


1 Answers

You need to call endInteractiveMovement in perfomBatchUpdates.

But whenever endInteractiveMovement triggered, cellForRow called. So cell will be refreshed and new cell will added(check with random color extension). To secure that, you need to save selectedCell in variable. And return that cell when endInteractiveMovement called.

Declare currentCell in ViewController

var isEnded: Bool = true
var currentCell: UICollectionViewCell? = nil

Store selected cell in variable when gesture began & call endInteractiveMovement in performBatchUpdates.

So, your handleLongGesture func look like below:

//Selectors
@objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {

    switch(gesture.state) {

    case .began:

        guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
            break
        }

        isEnded = false
        //store selected cell in currentCell variable
        currentCell = collectionView.cellForItem(at: selectedIndexPath)

        collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)

    case .changed:
        collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
    case .ended:

        isEnded = true
        collectionView.performBatchUpdates({
            self.collectionView.endInteractiveMovement()
        }) { (result) in
            self.currentCell = nil
        }

    default:
        isEnded = true
        collectionView.cancelInteractiveMovement()
    }
}

Also need to change cellForRow

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    if currentCell != nil && isEnded {
        return currentCell!
   } else {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
        cell.backgroundColor = .random
        return cell
    }
}

TIP

Use random color extension for better testing

extension UIColor {
    public class var random: UIColor {
        return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
    }
}

EDIT

If you have multiple sections. Lets take array of array

var data: [[String]] = [["1","2"],
                        ["1","2","3","4","5","6","7"],
                        ["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"]]

Then you need to maintain data when reordering

func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {

    print("\(sourceIndexPath) -> \(destinationIndexPath)")

    let movedItem = data[sourceIndexPath.section][sourceIndexPath.item]
    data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
    data[destinationIndexPath.section].insert(movedItem, at: destinationIndexPath.item)

}
like image 122
Jay Patel Avatar answered Oct 13 '22 09:10

Jay Patel