Hi friends of StackOverflow.
I have a chat screen on my app and I it perform a insertion and deletion based on the actual size of the an Array. Look this:
func addObject(object: Object?) {
if comments == nil || object == nil || object?.something == nil || object?.anything == nil {
return
}
self.objectsTableView.beginUpdates()
if self.objects!.count == 10 {
self.objects?.removeAtIndex(9)
self.objectsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow : 9, inSection: 0)], withRowAnimation: .Right)
}
self.objects?.insert(object!, atIndex: 0)
self.objectsTableView.insertRowsAtIndexPaths([NSIndexPath(forRow : 0, inSection: 0)], withRowAnimation: .Right)
self.objectsTableView.endUpdates()
}
But after some stress test, the log notify:
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (1) must be
equal to the number of rows contained in that section before the
update (10), plus or minus the number of rows inserted or deleted from
that section (1 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
I don't know whats happening, this happens only when the insert of objects is very extreme, like one per 0.2 seconds.
Someone know that I can do?
Model mismatch
The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted)
In plain English for the reasonable man, the UITableView thinks you should have 11 rows:
10 before the update + 1 inserted.
number of rows contained in an existing section after the update (1)
...refers to numberOfRowsInSection is returning 1 for section 0, which indicates that the objects array is out of sync, assuming you use something like below:
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return objects.count
}
Use NSFetchedResultsController
A clean solution is to use NSFetchedResultsController to be the interface between your model and the UI. It has well studied boilerplate code and is a great platform to ensure thread safety. Documentation here.
Note:
Neat effect! The cell seems to rotate around to the top.
I could not break it using the Gist you produced, nor scheduling multiple concurrent tests. There must be a rogue access to your Object array.
Demo
This simplified version works. Just hook doPlusAction to a button action and watch it loop:
class TableViewController: UITableViewController {
var objects:[Int] = [0,1,2,3,4]
var insertions = 5
#IBAction func doPlusAction(sender: AnyObject) {
tableView.beginUpdates()
objects.removeAtIndex(4)
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Right)
objects.insert(insertions++, atIndex: 0)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Right)
tableView.endUpdates()
let delay = 0.1 * Double(NSEC_PER_SEC) //happens the same with this too, when reach 100-150 items
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) { () -> Void in
self.doPlusAction(self)
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
cell.textLabel!.text = "Cell \(objects[indexPath.row])"
return cell
}
}
Name of the guy that solved the problem: Semaphore
The error still happens, but only with a high size of items on list. I don't know what can be.
The DataSource protocol:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = self.objects?.count ?? 0
if self.semaphore != nil && semaphoreCode == BLOCKED_STATE {
dispatch_semaphore_signal(self.semaphore!)
}
return count
}
The method that add object:
func addObject(object: Object?) {
if object == nil {
return
}
if self.semaphore != nil {
let tempSemaCode = dispatch_semaphore_wait(semaphore!, 100000)
if tempSemaCode == BLOCKED_STATE {
self.semaphoreCode = RELEASED_STATE
}
}
if self.objects != nil && semaphoreCode != BLOCKED_STATE {
var needsRemoveLastItem = false
if self.objects!.count == 10 {
self.objects?.removeAtIndex(9)
needsRemoveLastItem = true
}
self.objects?.insert(object!, atIndex: 0)
if self.objects!.count > 0 {
self.objectsTableView.beginUpdates()
if needsRemoveLastItem {
self.objectsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow : 9, inSection: 0)], withRowAnimation: .Right)
}
self.objectsTableView.insertRowsAtIndexPaths([NSIndexPath(forRow : 0, inSection: 0)], withRowAnimation: .Right)
self.objectsTableView.endUpdates()
self.semaphore = dispatch_semaphore_create(BLOCKED_STATE)
}
}
}
Related
I create a table view that every time I scroll to the end of table it will show shimmer in table section 1 for waiting the api load a new data. After api finish load data, new data will append to the array that use for contain all data that be show to table view on section 0, then reload the table view to update the section 1 numberOfRowsInSection to 0 to hide the shimmer and update the section 0 numberOfRowsInSection
So this is the example of my code
fileprivate var data = [dataArray]() {
didSet {
guard !data.isEmpty else {
return
}
tableView.reloadData()
tableView.layoutIfNeeded()
view.layoutIfNeeded()
}
}
fileprivate var isLoadingMore: Bool = false {
didSet {
tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
tableView.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height + 60.0
view.layoutIfNeeded()
}
}
fileprivate func loadData() {
if let size = self.paging.totalSize,
data.count >= size {
return
}
let limit = paging.limit
let offset = paging.offset
guard !isLoadingMore else { return }
isLoadingMore = true
controller.requestContent(
completion: { [weak self] (success, offset, size, data) in
DispatchQueue.main.async {
self?.isLoadingMore = false
guard let list = data,
let data = list as? [dataArray],
let size = size else {
return
}
if success {
if self?.paging.currentPage == 0 {
self?.data = data
if self?.scrollView.frame.size.height >= self?.scrollView.contentSize.height {
self?.paging.totalSize = size
self?.paging.currentPage += 1
self?.loadData()
return
}
} else {
self?.data.append(contentsOf: songs)
}
self?.paging.totalSize = size
self?.paging.currentPage += 1
} else {
self?.alert("failed")
}
}
})
}
fileprivate func loadDataFromCoreData() {
isLoadingMore = false
let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
let dataFromCoreData = Datas.fetchData(context: context).filter({$0.isSaved})
songs = dataFromCoreData.map({ dataArray(song: $0) })
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return data.count
case 1:
return isLoadingMore ? 1 : 0
default:
return 0
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height){
loadData()
}
}
func setupForCeckingAvailableData() {
utility.checkInternetAccess = { [weak self] result in
if result {
self?.paging = Paging()
self?.loadData()
} else {
self?.loadDataFromCoreData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupForCeckingAvailableData()
}
so some time when I first time load or try fetch new data by scrolling to the end of table I got a crash with the message is
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
So what the cause of it? Why got that crash even though I already use reloadData() every time I append a new data to variable data. Should I change reloadData() to
tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: .automatic)
tableView.endUpdates()
? if that so, is that the crash cause insertRows only refresh specific row and section so no take a loot of time to refresh table with a loot of datas??
That's all of my question, Thank you
I just want to point out something else.
Generally speaking, Singletons are Evil and I avoid them like the plague.
This is a real big code smell and would advise you to stop using this type of code:
TableView.beginUpdates()
TableView.insertRows(at: indexPaths, with: .automatic)
TableView.endUpdates()
See What is so bad about singletons? there are many more blog posts about it.
after I do some research I found a clue.
I add tableView.reloadData() before tableView.reloadSections(IndexSet(integer: 1), with: .automatic) because when I see the crashlytic crash happend at tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
I am building an iOS app, basically the user create an item by pressing the "+" button and then the app should put the new item in according section of the table based on the location of the item. However I got the error: Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update. Here is my code, thank you
let sections = ["Bathroom", "Bedroom", "Dining Room", "Garage", "Kitchen", "Living Room"]
#IBAction func addNewItem(_ sender: UIBarButtonItem) {
/*
//Make a new index path for the 0th section, last row
let lastRow = tableView.numberOfRows(inSection: 0)
let indexPath = IndexPath(row: lastRow, section: 0)
//insert this new row into the table
tableView.insertRows(at: [indexPath], with: .automatic)*/
//Create a new item and add it to the store
let newItem = itemStore.createItem()
var Num: Int = 0
if let index = itemStore.allItems.index(of: newItem) {
switch newItem.room {
case "Bathroom":
Num = 0
print("I am in here")
case "Bedroom":
Num = 1
case "Dining Room":
Num = 2
case "Garage":
Num = 3
case "Kitchen":
Num = 4
case "Living Room":
Num = 5
default:
Num = 0
print("I am in Default")
}
let indexPath = IndexPath(row: index, section: Num)
tableView.insertRows(at: [indexPath], with: .automatic)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create an instance of UITableViewCell, with default appearance
//let cell = UITableViewCell(style: .value, reuseIdentifier: "UITableViewCell")
//get a new or recycled cell
//if indexPath.row < itemStore.allItems.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
//Set the text on the cell with the decription of the item
//that is at the nth index of items, where n = row this cell
//will appear in on the table view
let item = itemStore.allItems[indexPath.row]
if item.name == "" {
cell.nameLabel.text = "Name"
} else {
cell.nameLabel.text = item.name
}
if item.serialNumber == nil {
cell.serialNumberLabel.text = "Serial Number"
} else {
cell.serialNumberLabel.text = item.serialNumber
}
cell.valueLabel.text = "$\(item.valueInDollars)"
cell.roomLabel.text = item.room
if item.valueInDollars < 50 {
cell.valueLabel.textColor = UIColor.green
}else if item.valueInDollars >= 50 {
cell.valueLabel.textColor = UIColor.red
}
cell.backgroundColor = UIColor.clear
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let LocationName = sections[section]
return LocationName
}
thank you so much for your time!
and this is how I create item
#discardableResult func createItem() -> Item {
let newItem = Item(random: false)
allItems.append(newItem)
return newItem
}
The problem is that you should insert the item in the array first:
itemStore.allItems.append(newItem)
Also, there is a difference between sections and rows in numberOfRowsInSection(return number of rows for every section) you have a switch that returns the same number, it should be
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
Edit :
The problem is when the table loads there is 0 rows for all the sections ( itemStore.allItems.count is zero ), when you try to insert a row say at section 0 , row 0 -- the dataSource must be updated only for that section , which is not happen in your case as it's the same array that is returned for all sections , so you must either have an array of array where inner array represent number of rows so addition/deletion from it doesn't affect other ones ,,,,, or lock the insert to say section 0 like this
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0 ) {
return itemStore.allItems.count
}
return 0
}
in this edit i inserted in 2 sections 0 and 2 with no crash because i handled numberOfRowsInSection to return old numbers for old section that why to be able to insert in all sections you must have a different data source array or manage from numberOfRowsInSection , see edited demo here Homepwner
Instead of setting footer in viewDidLoad implement this method
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if(section == 5 ) {
let textLabel = UILabel()
textLabel.text = "No more Item"
return textLabel
}
return nil
}
There is something terribly wrong with your data source. Your numberOfRow for all sections is the same. i.e. itemStore.allItems.count. Which means you are setting the same number of rows in each section.
The main issue is, while adding a new item, you are inserting a new row in the specific section which means
new number of rows for section = previous number of rows in section +
1
However, there is no addition in the data source.
So according to the code, you have inserted a new row, but your data source has one record less since you didn't add the item there.
So I would like you do the following in order of these steps :
Add item in required data source.
inserRowInSection
Update : Remove the condition in cellForRowAtIndexPath :
if indexPath.section <= 1 and it's else block. You don't need that. If number of sections is less than 1, it won't be called. Moreover it's the else block which is getting called all the time because you already have sections in your code. So if case will never be called.
So I got the issue. In itemStore.createItem() the value for room is always Room. In the switch-case, you never have Room case. So it always falls in default case. You need to fix that.
#Jacky.S I downloads your code and try to debug it.Fist look our error properly.
reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0)
So It says before update, your sections has 0 rows. Now you try to update you tableview using this piece of code.
print(indexPath)
tableView.insertRows(at: [indexPath], with: .automatic)
I just added the print statement for debugging purpose and it prints [0, 0] .So you say your tableview to insert 0th row on section 0.
Now when you insert any row into tableview it calls the delegate method.Now look at the below delegate method.
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
if section == 0{
print("0",itemStore.allItems.count)
}
if section == 1{
print("1",itemStore.allItems.count)
}
// print(itemStore.allItems)
return itemStore.allItems.count
}
In your code you just return itemStore.allItems.count.I added the above code for debugging.Now look that itemStore.allItems.count always return 1.So for first section it works perfectly because few times ago you insert a row in first section whose index is [0 = row, 0 = section].So you previously returned rows for section 0 is equal to the recently return rows for section 0.But When it comes for section 1 you previously return no row that means your section 1 has no rows in previous but in your above delegate method you also return 1 row for section 1.Which is conflicting because in previous you never update or return any rows for section 1.That's why you code crash and and say that
your number of rows contained in an existing section after the update is 1 which must be equal to the number of rows contained in that section before the update. Which is 0.
So this is the explanation for you crash.
Now, to recover from crash your allItems should be a dictionary or an array of array.
var allItems = [[Item]]()
or
var allItems = ["String":[Item]]()
guess allItems element is something like this
allItems = ["bathroom":[item1, item2, item3],"bedroom":[item1, item2, item3, item4]]
so here you have 2 section one is bathroom and another is bedroom.First section have 3 row and second one have 4 row and in you tableview delegate method you have to return 3 for section 0 and 4 for section 1
So my numberOfRowsInSection is:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let delegate = delegate else { return 0 }
return delegate?.comments.count + 1
}
I added one more to compensate for the "load more" which is on top of the table view (in this case, it's row 0).
Here's the thing: when I want to remove that cell "load more" after fetching the max amount of posts, it gives me an error:
The number of rows contained in an existing section after the update (12) must be equal to the number of rows contained in that section before the update (11), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
So here's the function that causes the error:
func loadMore() {
// loadMoreComments(completion:) inserts the new comment objects inside
// the data source in the beginning of the list.
delegate?.loadMoreComments(completion: { (isEndOfPosts, newCommentCount, comments) in
self.didReachEndOfPosts = isEndOfPosts
if isEndOfPosts {
// indexPaths just returns [[0,1]], or, just one row.
let indexPaths = self.createIndexPathsForNoMorePosts(newCommentCount: newCommentCount)
self.tableView.beginUpdates()
// error happens here.
self.tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
self.tableView.endUpdates()
// assume only one post comes in, and that's the last one.
self.tableView.beginUpdates()
self.tableView.insertRows(at: indexPaths, with: .automatic)
self.tableView.endUpdates()
}
}
What I'm trying to accomplish is this: once i get the last post, remove the "load more" cell, and insert the last few posts, replacing the 0 row with the first of the last few posts.
You need to notify your tableView's datasource about cell count change. Create a class variable, something like
var shouldShowLoadMoreCell = true
and than modify numberOfRowsInSection method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let delegate = delegate else { return 0 }
if shouldShowLoadMoreCell {
return delegate?.comments.count + 1
} else {
return delegate?.comments.count
}
}
finally, set this flag when needed
self.tableView.beginUpdates()
shouldShowLoadMoreCell = false
self.tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
self.tableView.endUpdates()
I have a tableview with two custom-cell xibs.
The first xib only contains uilabel and the second one only uibutton.
Once the uibutton is clicked the someTagsArray (array which i use for count in numberOfRows function) is appended and new rows should be inserted, but instead i'm getting this nasty error
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (8) must be
equal to the number of rows contained in that section before the
update (4), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
Here is my code (numberOfRowsInSection)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) ->
Int {
return someTagsArray.count + 1
}
cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.row < someTagsArray.count){
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = linesmain["start"]![indexPath.row]
return cell
} else {
var celle:vwAnswers = self.tableView.dequeueReusableCellWithIdentifier("cell2") as! vwAnswers
celle.Answer1.setTitle(answersmain["start"]![0], forState:UIControlState.Normal)
// answertitle is a global string variable
answertitle1 = "\(celle.Answer1.currentTitle!)"
return celle
}}
and finally the function code which crashes the app
func insertData(){
// appending the array to increase count
someTagsArray += linesmain[answertitle1]!
tableView.beginUpdates()
let insertedIndexPathRange = 0..<self.linesmain[answertitle2]!.count-4
var insertedIndexPaths = insertedIndexPathRange.map { NSIndexPath(forRow: $0, inSection: 0) }
tableView.insertRowsAtIndexPaths(insertedIndexPaths, withRowAnimation: .Fade)
tableView.endUpdates()
}
Thank you guys.
Try this code for inserting rows:
func insertData(){
let initialCount = someTagsArray.count as Int
let newObjects = linesmain[answertitle1] as! NSArray
// appending the array to increase count
//someTagsArray += newObjects
someTagsArray.addObjectsFromArray(newObjects)
self.tableView!.beginUpdates()
var insertedIndexPaths: NSMutableArray = []
for var i = 0; i < newObjects.count; ++i {
insertedIndexPaths.addObject(NSIndexPath(forRow: initialCount+i, inSection: 0))
}
self.tableView?.insertRowsAtIndexPaths(insertedIndexPaths as [AnyObject], withRowAnimation: .Fade)
self.tableView!.endUpdates()
}
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (5) must be
equal to the number of rows contained in that section before the
update (1), plus or minus the number of rows inserted or deleted from
that section (1 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
I'm trying to add rows to a table view when a user taps a row, to create an expandable section, however the extra rows aren't being counted before Xcode tries to add them in and as such causes this error (I think). Can anybody point me in the right direction?
// sectionExpanded is set to false in viewDidLoad. It is set to true when
// the user taps on the expandable section (section 0 in this case)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 && sectionExpanded {
return 5
} else {
return 1
}
}
// This should recount the rows, add the new ones to a temporary array and then add
// them to the table causing the section to 'expand'.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = menu[indexPath.row]
let cell = tableView.cellForRowAtIndexPath(indexPath) as MenuCell
if indexPath.section == 0 {
var rows: Int
var tmpArray: NSMutableArray = NSMutableArray()
sectionExpanded = !sectionExpanded
rows = tableView.numberOfRowsInSection(0)
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
} else {
delegate?.rightItemSelected(selectedItem)
}
}
It is telling you that you are trying to insert 1 new row, but numberofrows should be 5, before was 1 and you are trying to insert 1 new row, thats 2. Theres your problem.
rows = tableView.numberOfRowsInSection(0) //this returns 1
for i in 1...rows { //
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)//this will contain only 1 object, because the loop will run only for 1 cycle
}
EDIT
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = menu[indexPath.row]
let cell = tableView.cellForRowAtIndexPath(indexPath) as MenuCell
if indexPath.section == 0 {
var rows: Int
var tmpArray: NSMutableArray = NSMutableArray()
sectionExpanded = !sectionExpanded
rows = 1
if sectionExpanded {
rows = 5
}
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
} else {
delegate?.rightItemSelected(selectedItem)
}
}
Since you know number of rows will be always 5 or 1, you can try something like this. However, this is not a standard approach, I would suggest to alter your datasource array.
Here is some example how to do it: http://www.nsprogrammer.com/2013/07/updating-uitableview-with-dynamic-data.html its for Objective-C but you will get the gist of it.
You can try modifying the data source and then reload the table.
You should use insertRowsAtIndexPaths... and the like between a beginUpdates() and endUpdates(). The tableView will collect all the changes after beginUpdates() and then will apply them coherently after endUpdates(). So try something like:
tableView.beginUpdates()
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
tableView.endUpdates()
Remember that after the call to endUpdates() the number of sections and rows must be consistent with your model.
Since I don't know about your model, here's a simple example:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var sectionExpanded: Bool = false {
didSet {
if oldValue != sectionExpanded {
let expIndexes = map(0..<model.count) { r in
NSIndexPath(forRow: r, inSection: 0)
}
// Here we start the updates
tableView.beginUpdates()
switch sectionExpanded {
case false:
// Collapsing
tableView.deleteRowsAtIndexPaths(expIndexes, withRowAnimation: .Top)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Top)
default:
// Expanding
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Top)
tableView.insertRowsAtIndexPaths(expIndexes, withRowAnimation: .Bottom)
}
// Updates ended
tableView.endUpdates()
}
}
}
let model = ["foo", "bar", "zoo"]
//MARK: UITableView DataSource
struct TableConstants {
static let sectionCellIdentifier = "SectionCell"
static let expandedCellIdentifier = "ExpandedCell"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionExpanded ? model.count : 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch sectionExpanded {
case false:
let cell = tableView.dequeueReusableCellWithIdentifier(
TableConstants.sectionCellIdentifier,
forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "The Section Collapsed Cell"
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier(
TableConstants.expandedCellIdentifier,
forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "\(model[indexPath.row])"
cell.detailTextLabel?.text = "Index: \(indexPath.row)"
return cell
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
sectionExpanded = !sectionExpanded
}
}
Note that I moved the table updates to the sectionExpanded observer.
You already have 1 row in section = 0, and trying to insert 5 new rows. You can only add 4 rows more to map with numberOfRowsInsection.
Try following code:
sectionExpanded = !sectionExpanded
rows = self.numberOfRowsInSection(0)-1
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}