Collection view performBatchUpdates crash randomly in swift - ios

I am making chat application. for chat Ui, I used jsqmessageview controller. when we load previous messages then give the following error:-
[JSQMessagesCollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.52.10/UICollectionView.m:5867
2019-02-25 17:25:35.553375+0530 Calmex[288:12648] ** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 25 into section 0, but there are only 25 items in section 0 after the update'
we used following code :-
var indexPaths: [AnyObject] = []
var arraysizeOfCollectionview : Int = 0
if(isloadmore)
{
self.collectionView.performBatchUpdates
({
indexPaths.removeAll()
let lastIdx = messages123.count - 1
for i in 0...lastIdx
{
print("index path -->\(IndexPath.init(item: i, section: 0))")
indexPaths.append(IndexPath.init(item: i, section: 0) as AnyObject)
}
self.collectionView.insertItems(at: indexPaths as! [IndexPath])
self.collectionView.collectionViewLayout.invalidateLayout(with: JSQMessagesCollectionViewFlowLayoutInvalidationContext())
arraysizeOfCollectionview = arraysizeOfCollectionview + indexPaths.count
},
completion: {(finished) in
})
}
else
{
arraysizeOfCollectionview = self.messages.count
}
we have total row count in "arraysizeOfCollectionview" and return in the collection view.
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
// Return the number of messages
return arraysizeOfCollectionview
}
so anyone has a solution for this random crash then please help me or any idea of related to load the previous message using collection view then please help me.
thank you.

Related

Syncing UITableView with the Array

I have a UITableView which is shown when the screen is loaded. The UITableView shows 7 items since the dishes array contains 7 items. I have button which adds one more item to the dishes array.
// adding just one more item to the array
self.dishes.append(contentsOf: filteredArray)
let newIndexPath = IndexPath(row: self.dishes.count + 1, section: 0)
self.tableView.insertRows(at: [newIndexPath], with: .automatic)
As soon as the new dish is added I get the following error:
'NSInternalInconsistencyException', reason: 'attempt to insert row 10 into section 0, but there are only 9 rows in section 0 after the update'
*** First throw call stack:
I would like to see more of the code to understand what you're doing but I think you should be doing it more like this.
var dishes = [Dish]()
#IBAction func myButton(_ sender: Any) {
dishes.append(Dish())
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dishes.count
}
The last row index will be self.dishes.count without adding 1.

tableView.insertRows throws NSException, but not when `insertRows` at `[[0,0]]`

I am following a modified (simplified) version of the tutorial very much like what is found here:
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/ImplementNavigation.html#//apple_ref/doc/uid/TP40015214-CH16-SW1
and here is my UITableViewController:
import UIKit
class NewsTableViewController: UITableViewController {
#IBOutlet var newsTableView: UITableView!
/*
MARK: properties
*/
var news = NewsData()
override func viewDidLoad() {
super.viewDidLoad()
dummyNewData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return news.length()
}
// here we communicate with parts of the app that owns the data
override func tableView(_ tableView: UITableView
, cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
// note here we're using the native cell class
let cell = tableView.dequeueReusableCell(withIdentifier: "newsCell", for: indexPath)
// Configure the cell...
let row : Int = indexPath.row
cell.textLabel?.text = news.read(idx: row)
return cell
}
// MARK: Navigation ****************************************************************
// accept message from CreateNewViewController
#IBAction func unwindToCreateNewView(sender: UIStoryboardSegue){
if let srcViewController = sender.source as? CreateNewsViewController
, let msg = srcViewController.message {
// push into news instance and display on table
news.write(msg: msg)
let idxPath = IndexPath(row: news.length(), section: 1)
// tableView.insertRows(at: [idxPath], with: .automatic)
tableView.insertRows(at: [[0,0]], with: .automatic)
print("unwound with message: ", msg, idxPath)
print("news now has n pieces of news: ", news.length())
print("the last news is: ", news.peek())
}
}
/*
#DEBUG: debugging functions that display things on screen **************************
*/
// push some values into new data
private func dummyNewData(){
print("dummyNewData")
news.write(msg: "hello world first message")
news.write(msg: "hello world second message")
news.write(msg: "hello world third message")
}
}
The problem is in the function unwindToCreateNewView:
let idxPath = IndexPath(row: news.length(), section: 1)
tableView.insertRows(at: [idxPath], with: .automatic)
where news.length() gives me an Int that is basically someArray.count.
When I insertRows(at: [idxPath] ...), I get error:
libc++abi.dylib: terminating with uncaught exception of type NSException
But when I just hard code it to do:
tableView.insertRows(at: [[0,0]], with: .automatic)
It works just fine. And on the simulator I see new messages are inserted below the previous ones. What gives?
You have an "off by one" problem with the following code:
news.write(msg: msg)
let idxPath = IndexPath(row: news.length(), section: 1)
Let's say that just before this code is called, you have no items in news. This means there are 0 rows. When you want to add a new row, you need to insert at row 0 since row numbers start at 0.
Calling news.write(msg: msg) add the new item and its length is now 1.
Calling IndexPath(row: news.length(), section: 1) sets the row to a value of 1 but it needs to be 0.
One simple solution is to swap those two lines:
let idxPath = IndexPath(row: news.length(), section: 1)
news.write(msg: msg)
That will create the index path with the proper row number.
And since this is the first (and only) section, the section number needs to be changed to 0 in addition to the above change.
let idxPath = IndexPath(row: news.length(), section: 0)
news.write(msg: msg)

Crash when moving item from one UICollectionView Section to another, then deleting that item

I have a collectionView that has two sections, each filling up cells with separate arrays.
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 && GlobalList.priorityArray.count != 0 {
return GlobalList.priorityArray.count
} else if section == 1 && GlobalList.listItemArray.count != 0 {
return GlobalList.listItemArray.count
} else {
return 0
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections
}
I can move items between sections without any issues. I also can delete items. My problem occurs when I move an item between sections, reload data, then try to delete all the items in that section, I get the following error.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (3) must be equal to the number of items contained in that section before the update (3), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
Here are the methods I have created to deal with deleting and moving items
Moving Items:
override func collectionView(_ collectionView: UICollectionView,
moveItemAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
if (sourceIndexPath as NSIndexPath).section == 0 {
listItem = GlobalList.priorityArray.remove(at: sourceIndexPath.item)
} else if (sourceIndexPath as NSIndexPath).section == 1 {
listItem = GlobalList.listItemArray.remove(at: sourceIndexPath.item)
}
if (destinationIndexPath as NSIndexPath).section == 0 {
GlobalList.priorityArray.insert(listItem, at: destinationIndexPath.item)
} else if (destinationIndexPath as NSIndexPath).section == 1 {
GlobalList.listItemArray.insert(listItem, at: destinationIndexPath.item)
}
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reloadData), userInfo: nil, repeats: false)
}
Deleting Items:
func deleteItem(sender: UIButton) {
let point : CGPoint = sender.convert(.zero, to: collectionView)
let i = collectionView!.indexPathForItem(at: point)
print("deleting.. \(String(describing: i))")
let index = i
GlobalList.listItemArray.remove(at: (index?.row)!)
deleteItemCell(indexPath: i!)
}
func deleteItemCell(indexPath: IndexPath) {
collectionView?.performBatchUpdates({
self.collectionView?.deleteItems(at: [indexPath])
}, completion: nil)
}
Let me know if anything else is needed to figure this out.
Thanks in advance!
I also had a similar issue with the collectionView when calling reloadData and then directly after trying to insert cells into the collectionView. I solved this issue by instead manually deleting and inserting sections in the collectionView inside a perform batch updates, like so:
collectionView.performBatchUpdates({ [weak self] () in
self?.collectionView.deleteSections([0])
self?.collectionView.insertSections([0])
}, completion: nil)

Swift3 collectionView 'attempt to delete item 1 from section 1, but there are only 1 sections before the update'

My app crashed due to 'attempt to delete item 1 from section 1, but there are only 1 sections before the update'
I found other articles said that the data source must keep in sync with the collectionView or tableView, so I remove the object from my array: alarms before I delete the item in my collectionView, this works on didSelectItemAtIndexPath. But I don't know why this doesn't work.
I also tried to print the array.count, it showed the object did remove form the array.
func disableAlarmAfterReceiving(notification: UILocalNotification) {
for alarm in alarms {
if alarm == notification.fireDate {
if let index = alarms.index(of: alarm) {
let indexPath = NSIndexPath(item: index, section: 1)
alarms.remove(at: index)
collectionView.deleteItems(at: [indexPath as IndexPath])
reloadCell()
}
}
}
}
func reloadCell() {
userDefaults.set(alarms, forKey: "alarms")
DispatchQueue.main.async {
self.collectionView.reloadData()
print("Cell reloaded")
print(self.alarms.count)
}
}
You are having single section so you need to delete it from the 0 section. Also use Swift 3 native IndexPath directly instead of NSIndexPath.
let indexPath = IndexPath(item: index, section: 0)
alarms.remove(at: index)
DispatchQueue.main.async {
collectionView.deleteItems(at: [indexPath])
}
Edit: Try to break the loop after deleting items from collectionView.
if let index = alarms.index(of: alarm) {
let indexPath = IndexPath(item: index, section: 0)
alarms.remove(at: index)
DispatchQueue.main.async {
collectionView.deleteItems(at: [indexPath])
}
reloadCell()
break
}

reloadSections UITableView Swift

I am trying to reload section and update the number of cells in a section, but I'm getting an error and crash whenever I try. Here is the code I am using:
import UIKit
import CalendarView
import SwiftMoment
class TimeAwayRequestTableViewController: UITableViewController {
#IBOutlet var calendarView: CalendarView!
var selectedDates : [Moment] = []
override func viewDidLoad() {
super.viewDidLoad()
calendarView.delegate = self
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var returnInt = 0
if section == 0 {
returnInt = 2
}
if section == 1 {
returnInt = 1
}
if section == 2 {
print(selectedDates.count)
returnInt = selectedDates.count
}
return returnInt
}
}
extension TimeAwayRequestTableViewController : CalendarViewDelegate {
func calendarDidSelectDate(date: Moment) {
selectedDates.append(date)
print(selectedDates)
let section = NSIndexSet(index: 2)
self.tableView.beginUpdates()
self.tableView.reloadSections(section, withRowAnimation: .Automatic)
self.tableView.endUpdates()
}
func calendarDidPageToDate(date: Moment) {
print(date)
}
}
So basically when the date is tapped in the calendar, I add it to the Array and then update the number of cells. I haven't gotten to the point where I configure the cell content, right now I'm just trying to make sure this is working like i want. The error I get is:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
But i don't understand why because it counts and shows 2. So I'm confused.
I bet you used some arrays(including selectedDates) in func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell and it was beyond of bounds.Please check your data.

Resources