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.
Related
I want to understand what this error means?
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: was not
retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
for element kind 'UICollectionElementKindSectionHeader' at index path <NSIndexPath: 0x8aeb905cf5be0ed2>
{length = 2, path = 0 - 0}; supplementary view:
<UICollectionReusableView: 0x7f9236dc4ff0; frame = (0 0; 0 0); layer = <CALayer: 0x600001018620>>'
I am using custom header for UICollectionView.I am getting this crash as soon as the view is loaded. even before cellforrowatindexpath is called and the issue is not with the custom header, it is with the return UICollectionReusableView()
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader && indexPath.section == 2
{
return someCustomHeader
}
return UICollectionReusableView()
}
You must always obtain the section header with the method dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:, before calling it you must also register a header class. If you want to show only the header of the third section you must implement collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) to hide unwanted headers by giving them a .zero size (you can' t simply return an instance of UICollectionReusableView for unwanted headers).
import UIKit
class CollectionViewController: UICollectionViewController {
private let headerId = "headerId"
override func viewDidLoad(){
super.viewDidLoad()
// Registers a header class
self.collectionView.register(YourClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: self.headerId) // Replace YourClass with the name of your header class
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// This method must always call dequeueReusableSupplementaryView, even if section!=2
if kind == UICollectionView.elementKindSectionHeader{
return self.collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: self.headerId, for: indexPath)
}
return UICollectionReusableView()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{
// Shows only the header of the third section
return section == 2 ? desiredSize : .zero // Replace desiredSize with the size of the visible header
}
}
When you work with custom header you have to register first the header class.
collectionView.register(AppHeaderCollectionView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: AppHeaderCollectionView.headerIdentifier)
and after that for use the header you have to do it this.
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: AppHeaderCollectionView.headerIdentifier, for: indexPath) as! AppHeaderCollectionView
return header
}
After the iOS update to 15.0, I had to subclass UICollectionReusableView for my header view.
guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: MenuTitleHeaderView.sectionHeaderID, for: indexPath) as? MenuTitleHeaderView else {
fatalError("Unexpected Sumplementary View")
}
switch kind {
case UICollectionView.elementKindSectionHeader:
...
}
Don't forget to return the size for the header or footer as well.
One more thing, don't forget to return the number of sections. Or you will only see one section display.
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.
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
}
Trying to add a button "Add New" at the end of my UICollecitonView.
Working with CoreData.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionCell
configureCell(cell: cell, indexPath: indexPath)
var numberOfItems = self.collectionView(self.collectionView, numberOfItemsInSection: 0)
if (indexPath.row == numberOfItems - 1) {
var addCellButton = UIButton(frame: cell.frame)
addCellButton.setTitle("Add", for: UIControlState.normal)
cell.addSubview(addCellButton)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects + 1
}
return 0
}
Error: 'NSInvalidArgumentException', reason: 'no object at index 3 in section at index 0'
Do I need to check bounds of NSFetchedResultsController?
As proposed here: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 3 in section at index 0'
You're telling the UICollectionView that you have one extra item in the section than is stored in the NSFetchedResultsController.
I believe the exception is being thrown when you try to access NSFetchedResultsController.object(at: IndexPath) from the configureCell method for that "extra" item you said was there.
If you want the Add New button to appear in the same type of cell as all the others, the you should stop adding the extra item in collectionView(numberOfItemsInSection:)
Alternatively, you can create a different type of cell to put at the end, with just the Add New button, and move the if statement before you dequeue the cell. If you're populating the last item, you dequeue your "AddNew" cell identifier with the button. Otherwise you dequeue the "Cell" identifier and configure it to display the data from the NSFetchedResultsController.
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.