Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionViewDropDelegate wrong destination index path in `dropSessionDidUpdate`

I'm trying to implement Drag & Drop in this long narrow collection view: UICollectionView

It has horizontal layout with cells and sections of different size.

Drag interaction works well, but I noticed a problem in UICollectionViewDropDelegate:

func collectionView(
    _ collectionView: UICollectionView,
    dropSessionDidUpdate session: UIDropSession,
    withDestinationIndexPath destinationIndexPath: IndexPath?)
    -> UICollectionViewDropProposal {

    if let destination = destinationIndexPath {
        print(destination) // Prints WRONG index path!!!
        return UICollectionViewDropProposal(
            operation: .move, intent: .insertAtDestinationIndexPath
        )
    }

    return UICollectionViewDropProposal(
        operation: .cancel, intent: .unspecified
    )
}

Wrong destination index path is passed to collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:).
Because of that I can't correctly determine the section and decide whether the drop is available there.

like image 689
kelin Avatar asked Sep 21 '19 07:09

kelin


2 Answers

So, it's a bug of UIKit. The correct destination index path may be calculated as following:

func collectionView(
    _ collectionView: UICollectionView,
    dropSessionDidUpdate session: UIDropSession,
    withDestinationIndexPath destinationIndexPath: IndexPath?)
    -> UICollectionViewDropProposal {

    // Calculating location in view
    let location = session.location(in: collectionView)
    var correctDestination: IndexPath?

    // Calculate index inside performUsingPresentationValues
    collectionView.performUsingPresentationValues {
        correctDestination = collectionView.indexPathForItem(at: location)
    }

    guard let destination = correctDestination else {
        return UICollectionViewDropProposal(
            operation: .cancel, intent: .unspecified
        )
    }

    // check destination 
    // ...
}

To fix this bug, first, I tried to use the combination of location(in:) and indexPathForItem(at:). The resulting index path was equal to destinationIndexPath provided by the delegate method. Why? My attention was drawn by UIDataSourceTranslating. It's a protocol allowing collection and table views to show placeholder cells for drag & drop without changing the actual data source. And when the drag & drop interaction ends that placeholders are easily removed. So, I made an assumption that

  1. destinationIndexPath is calculated with help of indexPathForItem(at:)
  2. It ignores the placeholders created by UIDataSourceTranslating, which is a bug

Then I tried to wrap indexPathForItem(at:) into performUsingPresentationValues(_:) and the received index path was correct!

like image 116
kelin Avatar answered Sep 18 '22 12:09

kelin


So, it's a bug of UIKit. The correct destination index path may be calculated as following:

func collectionView(
    _ collectionView: UICollectionView,
    dropSessionDidUpdate session: UIDropSession,
    withDestinationIndexPath destinationIndexPath: IndexPath?)
    -> UICollectionViewDropProposal {

    // Calculating location in view
    let location = session.location(in: collectionView)
    var correctDestination: IndexPath?

    // Calculate index inside performUsingPresentationValues
    collectionView.performUsingPresentationValues {
        correctDestination = collectionView.indexPathForItem(at: location)
    }

    guard let destination = correctDestination else {
        return UICollectionViewDropProposal(
            operation: .cancel, intent: .unspecified
        )
    }

    // check destination 
    // ...
}

To fix this bug, first, I tried to use the combination of location(in:) and indexPathForItem(at:). The resulting index path was equal to destinationIndexPath provided by the delegate method. Why? My attention was drawn by UIDataSourceTranslating. It's a protocol allowing collection and table views to show placeholder cells for drag & drop without changing the actual data source. And when the drag & drop interaction ends that placeholders are easily removed. So, I made an assumption that

  1. destinationIndexPath is calculated with help of indexPathForItem(at:)
  2. It ignores the placeholders created by UIDataSourceTranslating, which is a bug

Then I tried to wrap indexPathForItem(at:) into performUsingPresentationValues(_:) and the received index path was correct!

like image 15
kelin Avatar answered Nov 16 '22 09:11

kelin