Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag and reorder - UICollectionview with sections

Is that possible to do drag and reorder from one section to another section from collectionview - iOS 9.

Every time am dragging from 1st section to drop at 4th section am getting below crash,

Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:

    func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath,toIndexPath destinationIndexPath: NSIndexPath)

        {

        let sequence = ArrayrelatedSequence[sourceIndexPath.section] as! SEQ_Sequence

        relIndexCards = sequence.rel_indexCard

        if relIndexCards.count > 0 {

            ArrayIndexcard = []

            let arr1: NSMutableArray = []

            for item in relIndexCards {

                arr1.addObject(item)
            }

            let descriptor: NSSortDescriptor = NSSortDescriptor(key: "keyP_IndexCard_ID", ascending: false)
            let arrNew:NSArray = arr1.sortedArrayUsingDescriptors([descriptor])

            let i = 0
            for item in arrNew
            {
                ArrayIndexcard.insert(item as! NSManagedObject, atIndex: i)
                i+1
            }

        }

        let temp = self.ArrayIndexcard.removeAtIndex(sourceIndexPath.item)

        self.ArrayIndexcard.insert(temp, atIndex: destinationIndexPath.item)


        if islongPressed_Indexcard == true
        {
            islongPressed_Indexcard = false

            let managedContext = appDelegate.managedObjectContext

            var i = 0

            for update in ArrayIndexcard
            {

                i = i+1

                update.setValue(i, forKey: "keyP_IndexCard_ID")

            }

            do {
                try managedContext.save()

                collectionview_in_outline.reloadData()

            } catch let nserror as NSError {

                print("Unresolved error \(nserror), \(nserror.userInfo)")
                abort()
            }

        }
    }

My flow layout code below,

 class CustomFlowLayout: UICollectionViewFlowLayout {

var longPress: UILongPressGestureRecognizer!
var originalIndexPath: NSIndexPath?
var draggingIndexPath: NSIndexPath?
var draggingView: UIView?
var dragOffset = CGPointZero

override func prepareLayout() {
    super.prepareLayout()

    installGestureRecognizer()
}

func applyDraggingAttributes(attributes: UICollectionViewLayoutAttributes) {
    attributes.alpha = 0
}

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let attributes = super.layoutAttributesForElementsInRect(rect)
    attributes?.forEach { a in
        if a.indexPath == draggingIndexPath {
            if a.representedElementCategory == .Cell {
                self.applyDraggingAttributes(a)
            }
        }
    }
    return attributes
}

override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
    let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
    if let attributes = attributes where indexPath == draggingIndexPath {
        if attributes.representedElementCategory == .Cell {
            applyDraggingAttributes(attributes)
        }
    }
    return attributes
}

func installGestureRecognizer() {
    if longPress == nil {
        longPress = UILongPressGestureRecognizer(target: self, action: #selector(CustomFlowLayout.handleLongPress(_:)))
        longPress.minimumPressDuration = 0.2
        collectionView?.addGestureRecognizer(longPress)
    }
}

func handleLongPress(longPress: UILongPressGestureRecognizer) {


    let location = longPress.locationInView(collectionView!)
    switch longPress.state {
    case .Began: startDragAtLocation(location)
    case .Changed: updateDragAtLocation(location)
    case .Ended: endDragAtLocation(location)
    default:
        break
    }
}

func startDragAtLocation(location: CGPoint) {
    guard let cv = collectionView else { return }
    guard let indexPath = cv.indexPathForItemAtPoint(location) else { return }
    guard cv.dataSource?.collectionView?(cv, canMoveItemAtIndexPath: indexPath) == true else { return }
    guard let cell = cv.cellForItemAtIndexPath(indexPath) else { return }

    originalIndexPath = indexPath
    draggingIndexPath = indexPath
    draggingView = cell.snapshotViewAfterScreenUpdates(true)
    draggingView!.frame = cell.frame
    cv.addSubview(draggingView!)

    dragOffset = CGPointMake(draggingView!.center.x - location.x, draggingView!.center.y - location.y)

    draggingView?.layer.shadowPath = UIBezierPath(rect: draggingView!.bounds).CGPath
    draggingView?.layer.shadowColor = UIColor.blackColor().CGColor
    draggingView?.layer.shadowOpacity = 0.8
    draggingView?.layer.shadowRadius = 10

    invalidateLayout()

    UIView.animateWithDuration(0.4, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: [], animations: {
        self.draggingView?.alpha = 0.95
        self.draggingView?.transform = CGAffineTransformMakeScale(1.2, 1.2)
        }, completion: nil)
}

func updateDragAtLocation(location: CGPoint) {
    guard let view = draggingView else { return }
    guard let cv = collectionView else { return }

    view.center = CGPointMake(location.x + dragOffset.x, location.y + dragOffset.y)



    if let newIndexPath = cv.indexPathForItemAtPoint(location) {   
             **cv.moveItemAtIndexPath(draggingIndexPath!, toIndexPath: newIndexPath)**
        draggingIndexPath = newIndexPath

    }
}

func endDragAtLocation(location: CGPoint) {
    guard let dragView = draggingView else { return }
    guard let indexPath = draggingIndexPath else { return }
    guard let cv = collectionView else { return }
    guard let datasource = cv.dataSource else { return }

    let targetCenter = datasource.collectionView(cv, cellForItemAtIndexPath: indexPath).center

    let shadowFade = CABasicAnimation(keyPath: "shadowOpacity")
    shadowFade.fromValue = 0.8
    shadowFade.toValue = 0
    shadowFade.duration = 0.4
    dragView.layer.addAnimation(shadowFade, forKey: "shadowFade")

    UIView.animateWithDuration(0.4, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: [], animations: {
        dragView.center = targetCenter
        dragView.transform = CGAffineTransformIdentity

    }) { (completed) in

        if !indexPath.isEqual(self.originalIndexPath!) {
            datasource.collectionView?(cv, moveItemAtIndexPath: self.originalIndexPath!, toIndexPath: indexPath)
        }

        dragView.removeFromSuperview()
        self.draggingIndexPath = nil
        self.draggingView = nil
        self.invalidateLayout()
    }

    NSNotificationCenter.defaultCenter().postNotificationName("Needtorefresh", object: nil)

    collectionView?.reloadData()

}

}

like image 222
vetrivel Avatar asked Aug 22 '16 13:08

vetrivel


People also ask

How do you create a collection view with multiple sections?

You need to implement a custom UICollectionViewLayout. With a horizontal flow layout it's going to fill from top to bottom first, then move to the right. Since you have two rows, as specified in sizeForItemAt, section 0 will fill from top to bottom, then right to left, and so will section 1.

How do you drag and drop Collection View cell in Swift?

Implement drag delegate First, we have to add a UICollectionViewDragDelegate protocol that uses to initiate drag from the collection view. Then we need to set its drag delegate at the init section to enable the drag operation of the collection view cell.

How do I add a section header in collection view?

There are no section headers in the UICollectionView. So for your first task, you'll add a new section header using the search text as the section title. To display this section header, you'll use UICollectionReusableView .

What is drag and drop collection?

The basic sequence involved in drag and drop is: Move the pointer to the object. Press, and hold down, the button on the mouse or other pointing device, to "grab" the object. "Drag" the object to the desired location by moving the pointer to this one. "Drop" the object by releasing the button.


1 Answers

Details

  • Xcode 10.2 (10E125), Swift 5

Links

Coding for iOS 11: How to drag & drop into collections & tables

Full Sample

ViewController

import UIKit

enum CellModel {
    case simple(text: String)
    case availableToDrop
}

class ViewController: UIViewController {

    private lazy var cellIdentifier = "cellIdentifier"
    private lazy var supplementaryViewIdentifier = "supplementaryViewIdentifier"
    private lazy var sections = 10
    private lazy var itemsInSection = 2
    private lazy var numberOfElementsInRow = 3

    private lazy var data: [[CellModel]] = {
        var count = 0
        return (0 ..< sections).map { _ in
            return (0 ..< itemsInSection).map { _ -> CellModel in
                count += 1
                return .simple(text: "cell \(count)")
            }
        }
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        let collectionViewFlowLayout = UICollectionViewFlowLayout()
        collectionViewFlowLayout.minimumLineSpacing = 5
        collectionViewFlowLayout.minimumInteritemSpacing = 5
        let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)
        let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)
        let width = (view.frame.width - allWidthBetwenCells)/_numberOfElementsInRow
        collectionViewFlowLayout.itemSize = CGSize(width: width, height: width)
        collectionViewFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
        collectionView.backgroundColor = .white
        view.addSubview(collectionView)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true

        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
        collectionView.register(SupplementaryView.self,
                                forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
                                withReuseIdentifier: supplementaryViewIdentifier)

        collectionView.dragInteractionEnabled = true
        collectionView.reorderingCadence = .fast
        collectionView.dropDelegate = self
        collectionView.dragDelegate = self

        collectionView.delegate = self
        collectionView.dataSource = self
    }
}

extension ViewController: UICollectionViewDelegate { }
extension ViewController: UICollectionViewDataSource {

    func numberOfSections(in collectionView: UICollectionView) -> Int { return data.count }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return data[section].count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        switch data[indexPath.section][indexPath.item] {
            case .simple(let text):
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
                cell.label?.text = text
                cell.backgroundColor = .gray
                return cell
            case .availableToDrop:
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
                cell.backgroundColor = UIColor.green.withAlphaComponent(0.3)
                return cell
        }
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: supplementaryViewIdentifier, for: indexPath as IndexPath) as! SupplementaryView
        return headerView
    }
}

extension ViewController: UICollectionViewDragDelegate {

    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = data[indexPath.section][indexPath.row]
        return [dragItem]
    }

    func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
        let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = data[indexPath.section][indexPath.row]
        return [dragItem]
    }


    func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {
        var itemsToInsert = [IndexPath]()
        (0 ..< data.count).forEach {
            itemsToInsert.append(IndexPath(item: data[$0].count, section: $0))
            data[$0].append(.availableToDrop)
        }
        collectionView.insertItems(at: itemsToInsert)
    }

    func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
        var removeItems = [IndexPath]()
        for section in 0..<data.count {
            for item in  0..<data[section].count {
                switch data[section][item] {
                    case .availableToDrop: removeItems.append(IndexPath(item: item, section: section))
                    case .simple: break
                }
            }
        }
        removeItems.forEach { data[$0.section].remove(at: $0.item) }
        collectionView.deleteItems(at: removeItems)
    }
}

extension ViewController: UICollectionViewDropDelegate {
    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
        let destinationIndexPath: IndexPath
        if let indexPath = coordinator.destinationIndexPath {
            destinationIndexPath = indexPath
        } else {
            let section = collectionView.numberOfSections - 1
            let row = collectionView.numberOfItems(inSection: section)
            destinationIndexPath = IndexPath(row: row, section: section)
        }

        switch coordinator.proposal.operation {
            case .move:
                reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
            case .copy:
                copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
            default: return
        }
    }

    func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool { return true }
    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
        if collectionView.hasActiveDrag, let destinationIndexPath = destinationIndexPath {
            switch data[destinationIndexPath.section][destinationIndexPath.row] {
                case .simple:
                    return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
                case .availableToDrop:
                    return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
            }
        } else { return UICollectionViewDropProposal(operation: .forbidden) }
    }

    private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
        let items = coordinator.items
        if  items.count == 1, let item = items.first,
            let sourceIndexPath = item.sourceIndexPath,
            let localObject = item.dragItem.localObject as? CellModel {

            collectionView.performBatchUpdates ({
                data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
                data[destinationIndexPath.section].insert(localObject, at: destinationIndexPath.item)
                collectionView.deleteItems(at: [sourceIndexPath])
                collectionView.insertItems(at: [destinationIndexPath])
            })
        }
    }

    private func copyItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
        collectionView.performBatchUpdates({
            var indexPaths = [IndexPath]()
            for (index, item) in coordinator.items.enumerated() {
                if let localObject = item.dragItem.localObject as? CellModel {
                    let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
                    data[indexPath.section].insert(localObject, at: indexPath.row)
                    indexPaths.append(indexPath)
                }
            }
            collectionView.insertItems(at: indexPaths)
        })
    }
}

Cells

import UIKit

class CollectionViewCell: UICollectionViewCell {
    weak var label: UILabel?

    override init(frame: CGRect) {
        super.init(frame: frame)
        clipsToBounds = true
        let label = UILabel(frame: .zero)
        label.contentMode = .scaleAspectFill
        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: topAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        label.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        label.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
        label.textAlignment = .center
        label.textColor = .white
        self.label = label
        layer.borderWidth = 1
        layer.borderColor = UIColor.white.cgColor
        backgroundColor = .white
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        label?.text = nil
        backgroundColor = .white
    }
}

import UIKit

class SupplementaryView: UICollectionReusableView {

    weak var label: UILabel?
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.blue.withAlphaComponent(0.7)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Result

enter image description here

like image 184
Vasily Bodnarchuk Avatar answered Nov 14 '22 17:11

Vasily Bodnarchuk