Reload Table view cell with animation (Swift) - ios

Is there a way to reload specific UITableView cells with multiple sections with animations?
I've been using:
self.TheTableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: UITableViewRowAnimation.Right)
This animates all the cells in the section though. The other option is to use:
self.TheTableView.reloadRowsAtIndexPaths(<#indexPaths: [AnyObject]#>,
withRowAnimation: <#UITableViewRowAnimation#>)
EDIT:
After using reloadRowsAtIndexPaths
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. 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 (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).'
Trying to find a smooth way of reloading a tableview by appending objects into an array.
Calling TheTableView.reloadData() before reloadRowsAtIndexPaths works, but the animation is glitchy. Is there another approach?

Why not just reload the cells directly? The indexPath contains the section and row so just keep track of which section you want to reload and fill the indexes in that array.
var indexPath1 = NSIndexPath(forRow: 1, inSection: 1)
var indexPath2 = NSIndexPath(forRow: 1, inSection: 2)
self.tableView.reloadRowsAtIndexPaths([indexPath1, indexPath2], withRowAnimation: UITableViewRowAnimation.Automatic)
Based on your comment you are looking to change your array and have the tableView animate in the changes. If that's the case you should consider using beginUpdates() and endUpdates() for UITableViews or even an NSFetchedResultsController so it handles all of the update animations cleanly.
self.tableView.beginUpdates()
// Insert or delete rows
self.tableView.endUpdates()
I'd recommend using NSFetchedResultsController if you're using Core Data as it simplifies this entire process. Otherwise you have to handle the changes yourself. In other words, if your array changes you need to manually remove or insert rows in the tableview using beginUpdates() and endUpdates().
If you aren't using Core Data, study this to grasp how it's all handled.

In Swift 3.0
We can reload particular row of particular section in tableview
let indexPath = IndexPath(item: 2, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .automatic)

If you want to reload single section in swift 3.0 you can do this:
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)

the define IndexSet in official document:
Manages a Set of integer values, which are commonly used as an index
type in Cocoa API. The range of valid integer values is
in (0, INT_MAX-1). Anything outside this range is an error.
so IndexSet is Set about index, just putting some Int values in [ ]
// for reload one section
tableView.reloadSections([section], with: .automatic)
// or using like this
tableView.reloadSections(IndexSet(integer: section), with: .automatic)
// for reload multi sections
tableView.reloadSections([1, 2, 3, section], with: .automatic)

Related

Without reloading Tableview, update the changed index cell values using Observer in iOS Swift

In the first time I collect the array values from API response and displayed it in tableview, again I will get the same response from socket and reload the values in table, But here I don't need to reload entire table, I want update the cell's which value has been changed.
Here Compare the two array's, from which index has changes, just need to update that index row cells only, without reload entire table view.old and new array, CodeSample
But you should be careful if will change some indexPaths in you data stack, if it gonna happen - use tableView.deleteRows or tableView.deleteSections. This updates should be qual in table and in dataStack or crash
let indexPaths = [IndexPath]()
tableView.performBatchUpdates {
self.tableView.reloadRows(at: indexPaths, with: .none)
}
or
tableView.beginUpdates()
tableView.reloadRows(at: indexPaths, with: .none)
tableView.endUpdates()
btw, you can make your indexPaths to update like let indexPaths = tableView.indexPathsForVisibleRows - method's name is speechful, in case if you have socket I suppose it would be helpful since u've got dynamic

How to append row in tableview swift?

I'm adding data in my model and model is assigned to tableview to reload data. But every time reloading is not looking good. so I want just last element that was added in model, should be appended in already exist tableview. Tried so many ways but getting crash when my tableview is empty.
let lastSectionIndex = self.isGroupChat ? self.objGroupChatList!.count-1 : self.objSingleChatList!.count-1
var lastRow = 0
if self.isGroupChat {
lastRow = (self.objGroupChatList?[lastSectionIndex].count ?? 1)
} else {
lastRow = (self.objSingleChatList?[lastSectionIndex].count ?? 1)
}
let IndexPathOfLastRow = IndexPath(row: lastRow-1, section: lastSectionIndex)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [IndexPathOfLastRow], with: UITableViewRowAnimation.none)
self.tableView.endUpdates()
This is crashing with error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of sections. The number of sections contained in the table
view after the update (1) must be equal to the number of sections
contained in the table view before the update (0), plus or minus the
number of sections inserted or deleted (0 inserted, 0 deleted).'
You should use insertSections for new sections. insertRows only works for existing sections.
You need to do something like,
let section = 0 //get your section here...
dataSource[section].append("five")
let row = dataSource[section].count - 1
tableView.insertRows(at: [IndexPath(row: row, section: section)], with: .none)
This is just an example of how you can get that working. Fill the gaps as per your code.

UITableView cellForRowAt before numberOfRowsInSection

So I have a view controller that has a table view. This view controller has a button that when clicked opens another view controller. There is a button on this view controller that will change the data set for the table view and dismiss the view controller.
Problem is when changing the data set and dismissing the view controller it calls cellForRowAt. But because the number of items has the potential to decrease I get an Index out of range error.
After setting some break points I realize this is because after updating and dismissing the view controller cellForRowAt gets called but numberOfRowsInSection doesn't. So the number of rows has updated but that isn't reflected in the table view.
I could do a check in cellForRowAt to see if it's out of range before hand and return an empty cell if that's the case, but that seems terribly inefficient. Although it's might be a good idea regardless, in this case seems like such a band-aid fix.
So how can I solve this in an effective and efficient manner?
There are two solutions to resolve this issue.
once the data set is updated just call the reloadData on your tableView which will reload all the data.
if some data is deleted then use deleteRows(at:with:) method .
for single row deletion
data.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
for multiple rows deletion with insertion
var indexPaths: [IndexPath] = []
for index in indexArray
data.remove(at: index)
indexPaths.append(IndexPath(item: index, section: 0))
}
if #available(iOS 11.0, *) {
self.tableView.performBatchUpdates({
self.tableView.deleteRows(at: indexPaths, with: .fade)
})
} else {
self.tableView.beginUpdates()
self.tableView.deleteRows(at: indexPaths, with: .fade)
self.tableView.endUpdates()
}
If you have multiple rows to be removed just create a array of index path with matching row index and pass it to the delete function.
Edit:
use batch updates only for multiple insert/delete/move operations only. as per the apple docs
UITableView defers any insertions of rows or sections until after it has handled the deletions/insertions of rows or sections. This order is followed regardless how the insertion and deletion method calls are ordered.
You can call tableView.reloadData in viewWillAppear method. So it will again reload full tableview with new data.

addNotificationBlock { RealmCollectionChange } crashes in UITableView

I'm using Realm for Swift and loading the data into a UITableView. There are roughly 200 data objects that are being gradually downloaded as I enter the screen, so there is a lot of insertion into the UITableView happening in my tests after the tableview has been displayed. I'm using the Realm example to addNotificationBlock with RealmCollectionChange as closely as I can and I'm getting two separate crashes that happen occasionally during this process.
*** Terminating app due to uncaught exception 'RLMException', reason: 'Can only add notification blocks from within runloops.'
This crash is occurring even though I make a point of pulling all the data from the main thread within my ViewController class.
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.30.14/UITableView.m:1720
**** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (27) must be equal to the number of rows contained in that section before the update (17), plus or minus the number of rows inserted or deleted from that section (9 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
This crash only started occurring after I replaced
tableView.reloadData()
with
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
tableView.endUpdates()
in the .Update() section of my addNotificationBlock
Is there something I'm missing about Realm that is causing this? I suspect it's due to me not fully understand the inner mechanisms of this library.
Here's my code for reference:
self.exhibits = DataManager.getAllExhibitsSorted("id")
token = self.exhibits?.addNotificationBlock { (changes: RealmCollectionChange) in
switch changes {
case .Initial(_):
self.exhibits = DataManager.getAllExhibitsSorted("id")
self.exhibitListTableView.reloadData()
break
case .Update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
self.exhibitListTableView.beginUpdates()
self.exhibitListTableView.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
self.exhibitListTableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
self.exhibitListTableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) },
withRowAnimation: .Automatic)
self.exhibitListTableView.endUpdates()
break
case .Error:
NSLog("Error in notificationBlock")
break
}
}
It sounds like you might be overcomplicating things a little here.
Realm Results objects are live and auto-updating. Meaning changes made to their underlying objects are updated automatically on the next iteration of the main run loop, so there's no need to perform a manual re-fetch on them. In your code there, you're re-assinging self.exhibits in the .Initial change notification, after the token has been generated, which may be causing some of your issues here. If you delete that line, it should just continue to work.
I'd recommend going through your code, and making sure that self.exhibits is only being assigned once, and that the change notification method is applied to just that one.
Let me know if that doesn't fix it.

InsertRowsAtIndexPaths error Swift

I have an uitableview and i try to append it by insertRowsAtIndexPaths function.
The uitableview appends nicely if i simply add data to the data source arrays, but gives me hard time inserting it by the mentioned above function.
Here is my code
func insertData(){
// appending arrays
answersdict.append(answersmain[answertitle1]![0]) // count 1
resultanswers2d.append(carsmain[answertitle1]!) // count 3
self.tableView.reloadData()
// inserting rows
self.tableView.beginUpdates()
let insertedIndexPathRange = 0..<carsmain[answertitle1]!.count + 1 // total count 4
var insertedIndexPaths = insertedIndexPathRange.map { NSIndexPath(forRow: $0, inSection: 1) }
self.tableView.insertRowsAtIndexPaths(insertedIndexPaths, withRowAnimation: .Fade)
self.tableView.endUpdates()
My datasource count for sections is 2 (so section 1 exists) and for numberOfRows - its equal to resultanswers2d[section].count + 1 which gives me 4
So i'm trying to insert 4 rows into the already appended array that has 4 possible rows in that section, but i'm constantly getting errors like
The number of rows contained in an existing section after the update
(4) must be equal to the number of rows contained in that section
before the update (4), plus or minus inserted cells (4 inserted, 0 deleted)
or if i try to hard
malloc_error
Got stuck. Need any insights. Thank you.
Delete the line to reload the table.
insertRowsAtIndexPaths updates the table accordingly.
The number of insertions and the positions in data source and table must match.
Simply don't call self.tableView.reloadData(). The beginUpdate and endUpdate sections should suffice.
UPDATE:
Also, remember to insert the new section too in the beginUpdate endUpdate block.

Resources