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()
}
}
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.
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.
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 .
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.
Coding for iOS 11: How to drag & drop into collections & tables
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)
}
}
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