DiffableDataSource - Is there a way to limit reordering only be performed within the same section?

Viewed 351

In https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views , Apple has shown a simple example on how to perform reordering using DiffableDataSource

ReorderableListViewController.swift

dataSource.reorderingHandlers.canReorderItem = { item in return true }
dataSource.reorderingHandlers.didReorder = { [weak self] transaction in
    guard let self = self else { return }
    
    // method 1: enumerate through the section transactions and update
    //           each section's backing store via the Swift stdlib CollectionDifference API

    if self.reorderingMethod == .collectionDifference {

        for sectionTransaction in transaction.sectionTransactions {
            let sectionIdentifier = sectionTransaction.sectionIdentifier
            if let previousSectionItems = self.backingStore[sectionIdentifier],
                let updatedSectionItems = previousSectionItems.applying(sectionTransaction.difference) {
                self.backingStore[sectionIdentifier] = updatedSectionItems
            }
        }
    
    // method 2: use the section transaction's finalSnapshot items as the new updated ordering
        
    } else if self.reorderingMethod == .finalSnapshot {

        for sectionTransaction in transaction.sectionTransactions {
            let sectionIdentifier = sectionTransaction.sectionIdentifier
            self.backingStore[sectionIdentifier] = sectionTransaction.finalSnapshot.items
        }
    }
}

Is there any way, to limit the reordering can only be performed within the same section?

There is not much we can do in reorderingHandlers.canReorderItem, because the closure parameter item is referring to the current source item we are dragging. There is no information on destination item, which we can compare with to decide whether to return true or false.

3 Answers

This behaviour isn't a question for your data source. It is a question for your delegate

You can use collectionView(_:targetIndexPathForMoveFromItemAt:toProposedIndexPath:) to determine whether a move is permitted

func collectionView(_ collectionView: UICollectionView, 
targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, 
         toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {

    let sourceSection = sourceIndexPath.section
    let destSection = proposedDestinationIndexPath.section

    var destination = proposedDestinationIndexPath

    if destSection < sourceSection {
        destination = IndexPath(item: 0, section: sourceSection)
    } else if destSection > sourceSection {
        destination = IndexPath(item: self.backingStore[sourceSection].count-1, section: sourceSection)
    }

    return destination
}

This constrains an item's movement to its own section.

If your Items had titles, here a different approach:

dataSource.reorderingHandlers.canReorderItem = {item in 
    let exclude = ["Library","Favorites","Recents","Search","All"]
    if(exclude.contains(item.title!)){return false}
    return true
}

You can also use such record in delegate method collectionView(_:targetIndexPathForMoveFromItemAt:toProposedIndexPath:)

func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
        if originalIndexPath.section != proposedIndexPath.section {
            return originalIndexPath
        }
        return proposedIndexPath
}

I read this in Matt Neuburg «Programming iOS 14» book and it works fine:)

Related