Moving UICollectionViewCell causes Error for invalidateItemsAtIndexPaths - ios

When dragging to move cells in a UICollectionView, the app is crashing due to items being marked as invalidated, but then missing at the index path.
The code successfully confirms performs the move in the collection. Then the collection returns the expected number of sections and the cells for the section. After seemingly completing without issue, the following error occurs:
2018-12-20 15:39:54.216391-0500 TestApp[1748:485235] *** Assertion failure in -[UICollectionViewData invalidateItemsAtIndexPaths:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore/UIKit-3698.93.8/UICollectionViewData.m:166
2018-12-20 15:39:54.216878-0500 TestApp[1748:485235] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempting to invalidate an item at an invalid indexPath: {length = 2, path = 0 - 1} globalIndex: 1 numItems: 0'
The code itself isn't doing anything very interesting. It makes sure that the last cell in edit mode (an add button) isn't moved, and then updates the data for our model.
It doesn't seem like any of the items in the collection should be invalidated in the first place. The number of items in the collection is fairly small, so everything is onscreen the entire time so no cells are being removed from view in this scenario. No content for any of the cells was changed, so I'm not entirely sure what could have been marked as invalid, but even with that occurring, I'm unaware why it would be missing.
Code below:
// MARK: UICollectionViewDataSource Functions
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let friendsCount = AppDataModel.sharedInstance.groupModels[groupIndex!].friends.count
if friendsCount >= 0 {
return isEditingEnabled ? friendsCount + 1 : friendsCount
} else {
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if isEditingEnabled && indexPath.row == AppDataModel.sharedInstance.groupModels[groupIndex!].friends.count {
if !isUpgraded {
var currentVideoCount = 0
for i in 0..<AppDataModel.sharedInstance.groupModels.count {
currentVideoCount += AppDataModel.sharedInstance.groupModels[i].friends.count
}
if currentVideoCount >= maxFreeVideos {
print("Max Videos for trial reached.")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "upgradeCell", for: indexPath)
return cell
}
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "addFriendCell", for: indexPath)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "friendCell", for: indexPath) as! MainViewFriendCollectionViewCell
let friendForCell = AppDataModel.sharedInstance.groupModels[groupIndex!].friends[(indexPath as NSIndexPath).row]
cell.setup(friendForCell, shouldHideDeleteButton: !isEditingEnabled, onDeleteFriend: self.onDeleteFriend, onForceUpload: self.onForceUpload)
return cell
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// The user cannot reorder the add button
let addButtonIndex = AppDataModel.sharedInstance.groupModels[groupIndex!].friends.count
let destinationIndex = (destinationIndexPath.item >= addButtonIndex) ? addButtonIndex - 1 : (destinationIndexPath as NSIndexPath).item
// reflect the changes to the view in the model
AppDataModel.sharedInstance.groupModels[groupIndex!].updateFriendOrdersAfterReorder((sourceIndexPath as NSIndexPath).item, toIndex: destinationIndex)
}
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
let addButtonIndex = AppDataModel.sharedInstance.groupModels[groupIndex!].friends.count
return proposedIndexPath.item >= addButtonIndex ? originalIndexPath : proposedIndexPath
}

Related

Invalid update: invalid number of items in section 0. datasource updated first

I have read and looked over the several posts that mentions this. I tried my best to follow along but I am still having an issue.
self.items.append(contentsOf: newItems)
let newIndexPath = IndexPath(item: self.items.count - 1, section: 0)
DispatchQueue.main.async {
self.collectionView.insertItems(at: [newIndexPath])
}
Items is the array where I have all of the items, I am adding newItems. I did a print and I know there is new items. So for newIndexPath it would be the next items.count - 1. I tried using self.items.count - 1 and self.collectionView.numberOfItems(inSection: 0)
Are you using below delegates methods? Your code is very limited and doesn't give much information. However, I think you are trying to update the number of Items in section of collectionView without reloading the collectionView data.
Better to do below:
extension ViewController : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
else { fatalError("Unexpected cell in collection view") }
cell.item = self.items[indexPath.row]
return cell
}
If you are updating the items array, then append the new item and reload the collectionView will update the list. You can do as below:
self.items.append(contentsOf: newItems)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
No need to insert new item in collectionView.

Get next cell in UIcollectionView in Swift

I need to get the next cell inside cellForItem within a collection view so that I can update a view object. When I try the following below it doesn't work. I've also tried indexPathForVisibleItems passing in indexPath.row + 1 and the produces an index out of range error.
let index = IndexPath(row: indexPath.row + 1, section: indexPath.section)
if let nextCell = collectionView.cellForItem(at: index) as! MKRCell {
nextCell.setupWaitView(time: timeToWait)
nextCell.waitViewHeightConstraint.constant = 80
nextCell.waitView.alpha = 1
nextCell.waitView.isHidden = false
}
Is this possible to achieve or will I need to do this via another way?
Thanks
No, it is not possible to get the cell object before initialization in cellForItemAt but here
you can receive the call before displaying the cell from UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView,willDisplay cell: UICollectionViewCell,forItemAt indexPath: IndexPath) {
if let myCell = cell as? MKRCell {
}
}
AND
If you want to set up the cell you have to setup view in the UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
You should update the cell in:
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Remember to modify only the cell you'll be returning from this method. Other cells might not have exist at that moment.
Alternatively you can keep a weak reference to the cell and update it when needed.

Crashing while collection view cell deletion because of "UICollectionViewLayoutAttributes"

My data source is a 2-dimensional array because I have emojis divided into sections.
eg. [[a,b,c,d],[e,f,g],[h,j,k,l,m]]
My cell deletion code -
func deleteEmoji(at indexPath: IndexPath) {
// Get the bad emoji
let emojiToBeDeleted = emojisArray[indexPath.section][indexPath.row]
// Delete the emoji from datasource and collection view
emojisArray[indexPath.section].remove(at: indexPath.row)
stickerCollectionView.deleteItems(at: [indexPath])
// Delete the section from datasource and collection view as its emoji count is zero
if emojisArray[indexPath.section].count == 0 {
emojisArray.remove(at: indexPath.section)
stickerCollectionView.deleteSections(IndexSet(integer: indexPath.section)) // Crashing here :(
sectionCollectionView.reloadData()
}
// Delete the bad emoji
emojiToBeDeleted.delete()
// Undo the deletion mode as all emoji's are deleted
if emojisArray.count == 0 {
isDeletionMode = false
}
}
I also have footers for every section, except for the last section, for that I have this in UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionFooter:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: Identifier.footer, for: indexPath)
return headerView
default:
assert(false, "Unexpected element kind")
}
}
and in UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
switch collectionView {
case stickerCollectionView:
if section == emojisArray.count - 1 {
return CGSize.zero
}
return CGSize(width: collectionView.bounds.size.width, height: 15)
default:
return CGSize.zero
}
}
Error -
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no
UICollectionViewLayoutAttributes instance for
-layoutAttributesForSupplementaryElementOfKind: UICollectionElementKindSectionFooter at path {length = 2, path = 0 - 0}'
I am getting this error while deleting cells. And crash doesn't happen always, it is quite random.
sectionCollectionView is completely different UICollectionView.

Updating collection view items

I have inserted a collectionView inside my tableViewCell. Tableview contains the list of categories and the collectionView contains all the product. How can I have a different number of items in the collectionView based off of which table view row was selected? I've tried storing the selected table view row and using that to define the number of items to be returned however it either crashes with no error code, tells me the value is nil or just does not display any clitems in the collectionView. Any help would be greatly appreciated. Thank you for your time.
Below is my code:
My Custom table view cell:
extension ExpandableCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let toReturn = categoryItems.count
return counter
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CustomCollectionViewCell
//What is this CustomCollectionCell? Create a CustomCell with SubClass of UICollectionViewCell
//Load images w.r.t IndexPath
print(self.selectedCategory.description)
let newArray = starbucksMenu[selectedCategory]
//cell.image.image = UIImage(named: (allItems[selectedCategory]?[indexPath.row])!)
cell.label.text = categoryItems[indexPath.row]
//cell.layer.borderWidth = 0.1
return cell
}
My table view delegate method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
word = indexPath.row
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
switch cell.isExpanded
{
case true:
self.expandedRows.remove(indexPath.row)
self.selectedCategory = ""
case false:
self.expandedRows.insert(indexPath.row)
}
self.selectedCategory = categories[indexPath.row]
print(self.selectedCategory)
//self.array = starbucksMenu[starbucksMenuCategories[indexPath.row]]!
//self.collectionView.reloadData()
cell.menuItems = allItems[selectedCategory]!
cell.categoryItems = allItems[selectedCategory]!
cell.isExpanded = !cell.isExpanded
self.itemsArray = allItems[selectedCategory]!
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
I've tried many things, I've tried adding the items in an array and returning the count (displays nothing). I have a dictionary with the necessary items so I've also tried returning allItems[selectedCategory]?.count and this always returns an error, I believe selectedCategory has no value once this is called.
Make a for loop of collection view item with appropriate operation Between beginUpdate() and endUpdate()

Assertion failure in -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:]

I am creating a table view and inside a custom UItableviewCell I am using collection view and I also create a custom collectionview cell. After setting all the basic functionalities of collection view in TableViewCell, When run the app I got the crash with reason:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:cellForItemAtIndexPath: ( {length = 2, path = 0 - 0}) was not retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath: or is nil (>)'
I tried to search more for it but can't find any direction
Here is my code snippet:
1. In TableViewcell awakeFrom Nib:
override func awakeFromNib() {
super.awakeFromNib()
// self.collectionView_Files.registerNib(UINib(nibName: "MediaCollectionCell", bundle: nil), forCellWithReuseIdentifier: "MediaCollectionCell")
self.collectionView_Files.registerClass(MediaCollectionCell.self, forCellWithReuseIdentifier: "MediaCollectionCell")
}
CollectionView methods:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrFolderData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let simpleTableIdentifier = "MediaCollectionCell"
var cell: MediaCollectionCell = collectionView.dequeueReusableCellWithReuseIdentifier(simpleTableIdentifier, forIndexPath: indexPath) as! MediaCollectionCell
cell = NSBundle.mainBundle().loadNibNamed(simpleTableIdentifier, owner: self, options: nil)[0] as! (MediaCollectionCell)
let dict = arrFolderData[indexPath.row]
if(dict["file_type"] as! String == "0") { /// Means image
cell.imgView_Item.image = UIImage(named: "images_41")
cell.btn_ViewImage.hidden = false
cell.btn_PlayVideo.hidden = true
} else if (dict["file_type"] as! String == "1") { /// Means video
cell.btn_ViewImage.hidden = true
cell.btn_PlayVideo.hidden = false
cell.imgView_Item.image = UIImage(named: "video_thumb_icon")
}
// cell.backgroundColor = arrFolderData[indexPath.item]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Collection view at row \(collectionView.tag) selected index path \(indexPath)")
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
let length = (UIScreen.mainScreen().bounds.width-15)/2
return CGSizeMake(length,length);
}
As mentioned in the post, I was working in tablecell xib file and try to do as I need, however once I change my approach and I create tableview cell into the UITableView inside the storyboard and update all the outlets and then run the app. The app was working fine....all the outlets in collection view cell are dynamically created with taking reference from their static variables.

Resources