I'm trying to implement Drag & Drop in this long narrow collection view:
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.
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
destinationIndexPath
is calculated with help of indexPathForItem(at:)
Then I tried to wrap indexPathForItem(at:)
into performUsingPresentationValues(_:)
and the received index path was correct!
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
destinationIndexPath
is calculated with help of indexPathForItem(at:)
Then I tried to wrap indexPathForItem(at:)
into performUsingPresentationValues(_:)
and the received index path was correct!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With