简体   繁体   中英

Am I correct in thinking collection view drag and drop + compositional layout just don't work together?

Update on July 8 2022 - Apple appears to have fixed the two finger scrolling bug, although the interaction is still a bit buggy.


Collection view + compositional layout + diffable data source + drag and drop does not seem to work together. This is on a completely vanilla example modeled after this (which works fine.)

Dragging an item with one finger works until you use a second finger to simultaneously scroll, at which point it crashes 100% of the time. I would love for this to be my problem and not an Apple oversight.

I tried using a flow layout and the bug disappears. Also it persists even if I don't use the list configuration of compositional layout, so that's not it.

Any ideas? Potential workarounds? Is this a known issue?

(The sample code below should run as-is on a blank project with a storyboard containing one view controller pointing to the view controller class.)

import UIKit

struct VideoGame: Hashable {
    let id = UUID()
    let name: String
}

extension VideoGame {
    static var data = [VideoGame(name: "Mass Effect"),
                       VideoGame(name: "Mass Effect 2"),
                       VideoGame(name: "Mass Effect 3"),
                       VideoGame(name: "ME: Andromeda"),
                       VideoGame(name: "ME: Remaster")]
}



class CollectionViewDataSource: UICollectionViewDiffableDataSource<Int, VideoGame> {

    // 1
    override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        guard let fromGame = itemIdentifier(for: sourceIndexPath),
              sourceIndexPath != destinationIndexPath else { return }
        
        var snap = snapshot()
        snap.deleteItems([fromGame])
        
        if let toGame = itemIdentifier(for: destinationIndexPath) {
            let isAfter = destinationIndexPath.row > sourceIndexPath.row
            
            if isAfter {
                snap.insertItems([fromGame], afterItem: toGame)
            } else {
                snap.insertItems([fromGame], beforeItem: toGame)
            }
        } else {
            snap.appendItems([fromGame], toSection: sourceIndexPath.section)
        }
        
        apply(snap, animatingDifferences: false)
    }
}






class DragDropCollectionViewController: UIViewController {
    
    var videogames: [VideoGame] = VideoGame.data
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout.list(using: UICollectionLayoutListConfiguration(appearance: .insetGrouped)))
    
    lazy var dataSource: CollectionViewDataSource = {
        
        let dataSource = CollectionViewDataSource(collectionView: collectionView, cellProvider: { (collectionView, indexPath, model) -> UICollectionViewListCell in

            return collectionView.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: model)

            })

        return dataSource
    }()
    
    let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, VideoGame> { (cell, indexPath, model) in

        var configuration = cell.defaultContentConfiguration()
        configuration.text = model.name
        cell.contentConfiguration = configuration
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.frame = view.bounds
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        collectionView.dragDelegate = self
        collectionView.dropDelegate = self
        collectionView.dragInteractionEnabled = true
        
        var snapshot = dataSource.snapshot()
        snapshot.appendSections([0])
        snapshot.appendItems(videogames, toSection: 0)
        dataSource.applySnapshotUsingReloadData(snapshot)
    }
}

extension DragDropCollectionViewController: UICollectionViewDragDelegate {
    
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let item = dataSource.itemIdentifier(for: indexPath) else {
            return []
        }
        let itemProvider = NSItemProvider(object: item.id.uuidString as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = item

        return [dragItem]
    }
}

// 4
extension DragDropCollectionViewController: UICollectionViewDropDelegate {
    
    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
        return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }

    
    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
        //Not needed
    }
}

If you download the modern Collectionviews project from Apple, there is one that shows compositional layout, diffable datasource and reordering. However this is only for their new list cells, not a reg CollectionView cell.

You can find it here: Modern CollectionViews

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM