6
votes

So I've implemented drag & drop for my UICollectionView like this:

func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard shouldAllowDragForIndexPath?(indexPath) == true else { return [] }
        guard let cell = collectionView.cellForItem(at: indexPath)?.toImage() else {
            return []
        }
        let provider = NSItemProvider(object: cell)
        let item = UIDragItem(itemProvider: provider)
        item.localObject = data[indexPath.row]

        return [item]
    }

    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
        guard
            let item = coordinator.items.first?.dragItem,
            let sourceIndexPath = coordinator.items.first?.sourceIndexPath,
            let destinationIndexPath = coordinator.destinationIndexPath,
            let entity = item.localObject as? T
            else {
                return
        }
        collectionView.performBatchUpdates({ [unowned self] in
            dropEntityAtIndexPath?(entity, destinationIndexPath)
            self.collectionView.deleteItems(at: [sourceIndexPath])
            self.collectionView.insertItems(at: [destinationIndexPath])
        }, completion: nil)
        coordinator.drop(item, toItemAt: destinationIndexPath)
        item.localObject = nil
    }

    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
        if
            session.localDragSession != nil,
            let path = destinationIndexPath,
            shouldAllowDropForIndexPath?(path) == true {
            return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        } else {
            return UICollectionViewDropProposal(operation: .forbidden)
        }
    }

Everything working but I've spotted strange behavior when the cell is being reused. Here is video: link

So it only happens after cells were being dragged, also it seems like there is some gray overlay view, but I cannot see it on view debugger. Also, I've tried to set my cell's imageView.image = nil in prepareForReuse but it didn't help. I would be very grateful if someone helps me fix this. Thanks.

Edit1: Code in cellForItemAt:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: PhotoCollectionViewCell.self)
        let container = data[indexPath.row].postContainer,
        let post = container.postMedia.first
        let isCarousel = container.isCarousel
        let isVideo = post.isVideo
        let _isSelected = isSelected?(indexPath) ?? false
        let url = post.thumbURL?.toURL()
        let isScheduled = container.schedulerFor != nil
        let fromInstagram = false

        cell.configure(url: url, isSelected: _isSelected, isVideo: isVideo, isCarousel: isCarousel, isScheduled: isScheduled, fromInstagram: fromInstagram)

    return cell
}

Edit 2 Cell configuration function:

func configure(url: URL?, isSelected: Bool = false, isVideo: Bool = false, isCarousel: Bool = false, isScheduled: Bool = false, fromInstagram: Bool = false) {
        checkmarkImageView.isHidden = !isSelected
        selectionOverlay.isHidden = !isSelected
        isVideoImageView.isHidden = !isVideo
        carouselImageView.isHidden = !isCarousel
        cornerImageView.image = isScheduled ? R.image.cyanCorner() : R.image.grayCorner()
        instagramImageView.isHidden = !fromInstagram
        cornerImageView.isHidden = fromInstagram
        url.map(imageView.setImage(with:))
    }
1
What does your cellForItem(at:) function look like?Jacob Relkin
@JacobRelkin Added to the question.Bohdan Savych
...and what does your configure function look like?Jacob Relkin
@JacobRelkin Added to the question.Bohdan Savych
@JacobRelkin Always after scrolling if drag & drop happened before.Bohdan Savych

1 Answers

0
votes

I think the problem is not a cell reuse, but improper model update. item.localData should be updated properly when movement is finished.

It means that you must update your data model object before you call UICollectionView update.

Here is the example for UICollectionViewDelegate, but only these line actually matters:

// YOUR MODEL OBJECT is expected to be a list-like data structure
YOUR_MODEL_OBJECT.insert(YOUR_MODEL_OBJECT.remove(at: sourceIndex), at: destinationIndex)

How it works for UICollectionViewDelegate

func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    guard destinationIndexPath != sourceIndexPath else { return }
    // YOUR MODEL OBJECT is expected to be a list-like data structure
    YOUR_MODEL_OBJECT.insert(YOUR_MODEL_OBJECT.remove(at: sourceIndex), at: destinationIndex)
}