ReloadRowsAtIndexPaths with no Row Animation animates - uitableview

The problem with this code (inside func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath))
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
if indexPath.row > 1{
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: indexPath.row-1, inSection: 0)], withRowAnimation: .None)
}
if tDate[activeRow].count == 0{
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .None)
}
is that both reloadRowsAtIndexPaths are animated, although withRowAnimation: .None is specified. What am I missing here?

It is often the case in iOS and OS X that you end up with an implicit animation for one reason or another (for example, the code is being executed by other code that has already triggered animation).
It can be especially difficult to control animations with UITableView and UICollectionView in my experience. Your best bet is probably to put the calls to reloadRowsAtIndexPaths:withRowAnimation: into a closure passed to UIView's performWithoutAnimations: method:
UIView.performWithoutAnimation {
if indexPath.row > 1{
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: indexPath.row-1, inSection: 0)], withRowAnimation: .None)
}
if tDate[activeRow].count == 0{
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .None)
}
}
Note: It's also not a bad idea to call tableView.beginUpdates() and tableView.endUpdates() before and after multiple updates that happen all at once.

While an appropriate solution for this problem was described by Charles, it did not answer the question as to why UITableViewRowAnimation.none provides an animation.
According to the docs for UITableViewRowAnimation.none, "The inserted or deleted rows use the default animations." Therefore, while .none sounds like there shouldn't be an animation, it actually acts more like .automatic.
I would have left this as a comment, but comments cannot contain pictures. Please note that this answer is not meant to solve the problem as Charles has already done that. This is strictly for reference for the next person wondering why UITableViewRowAnimation.none provides an animation.

Related

How to fix voiceover after deleting a row in a TableView

I've got some problems fixing voiceover after deleting a row. the structure is like this:
I've got a tableview with 2 sections.
The first section got a header of height = 0 and only one row of variable height.
The second section got a header with fixed height with a button inside; rows in this sections can be 'n'.
When the user tap the button inside the header the cell in the first section is deleted or re-inserted according to the previous state.
In the normal 'state' with the cell expanded the voiceover works perfectly. When the user taps the button and delete the row in the first section the voiceover breaks. If I browse from top to bottom it's all ok. Instead when you scroll upwards the vo reads the cells visible on the screen but reads the header of the first section before the cells below it.
Insert and deleting is pretty simple:
let indexPath = IndexPath(row: 0, section: 0)
if isExpanded {
if tableView.contentOffset.y <= 0 {
tableView.insertRows(at: [indexPath], with: .automatic)
} else {
tableView.reloadData()
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
} else {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
cells in each sections have: isAccessibilityElement = false
the accessibility element is the card inside the cell like this:
cardView.isAccessibilityElement = true
cardView.accessibilityTraits = .button
I would really appreciate your help, I have tried different solutions but none of them work. It's pretty much a headache!
Let me know if you need more info to solve this.
Thank you.
Solved this by keeping always a row in the first section.
tableView.performBatchUpdates({
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.insertRows(at: [indexPath], with: .fade)
}, completion: nil)
But when the variable isExpanded is false the height of the rows in first section is set to 0 instead of automatic.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return isExpanded ? UITableView.automaticDimension : 0
}
return UITableView.automaticDimension
}

How to select another row right after deleting row in tableView (Swift)

I am deleting row and want to get next behavior:
If I am deleting row that was selected, I need to get another row selected automatically.
And also if I have an additional trouble, when I am perform deleting, target row that is going to be deleted selecting automatically, I don't need it to be selected as I want to only delete it.
The main question is 1 for now.
I understand that maybe row: 0 does not exists anymore but t's not a point now, I just simplified code to make it more comfortable to discuss the point. I tried performBatchUpdates, and tried to move selectRow(at:section:) to completion closure as well, row selection not works too.
selectRow works perfectly after insertRows function, so I am confused.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .middle)
}
}
As I understood your question, find this solution:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
Pass indexPath while deleting a row and after endUpdates, you just need to set the same indexPath as SelectedRow.
Ok, I've found solution for the 1st question (just thought maybe such function can exist and typed didrowat, and xcode suggested this didEndEditingRowAt :)
override func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .middle)
}
Now first part of question is answered, I've inserted selection there.
The second question is still opened, how to make selected row keeping still selected while I am removing another row. I don't need selected row to deselect at all if I am removing another one.

Swift: Reorder Table Rows programmatically

I have a TableViewController with rows that hold tasks. If a task has an attribute task.done = 1 I want to move it at the bottom from the table.
I can't provide any code, because I have no doubt how to do this.
My idea was in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) using following code:
let element = tasks.remove(at: indexPath.row)
tasks.insert(element, at: tasks.count)
The issue is, that this needs to be done after the table is loaded, because if the first row is done = 1 for example, it will be moved to to bottom and will be processed at the end again.
You can programmatically remove a row from UITableView and insert a row programmatically. Before doing the operations on the UITableView, make sure to remove/add a specific item to the data source array. Otherwise, it'll just crash.
If you want to simply move the rows, you can use the code below. You need to do it in the place where the array which holds the data source is updated.
tableView.moveRow(at: oldIndexPath, to: newIndexPath)
If you want to do delete and insert new objects into the array, you may try the method as shown below.
let element = tasks.remove(at: indexPath.row)
tableView.deleteRows(at: indexPath, with: .automatic)
tasks.insert(element, at: tasks.count)
tableView.insertRows(at: [IndexPath(row: tasks.count, section: 0)], with: .automatic)
Wrap your code in a beginUpdates-endUpdates block.Use func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
For example: Not tested
self.tableView.beginUpdates()
let element = tasks.remove(at: indexPath.row)
let movingRowIndex = indexPath.row
tasks.insert(element, at: tasks.count)
let indexPath = NSIndexPath(forRow: movingRowIndex, inSection: 0)
var lastIndexPath = NSIndexPath(forRow:tasks.count, inSection: 0)
self.tableView.moveRow(at indexPath: IndexPath, to newIndexPath: lastIndexPath)
self.tableView.endUpdates()

iOS 11 UITableViewCell above viewForHeaderInSection view after deleting

I have simple UITableViewController.
View for header. Which inits from xib.
And single type of cell.
After deleting cell with swipe, cell which above deleted one become visible above HeaderView, when other cells just hides below HeaderView as it should be.
If something above not clear - ask.
Video: https://youtu.be/aX-iPnM3q4Q
I think this should solve it:
tableView.sendSubview(toFront: tableView.headerView(forSection: 0)!)
(assuming you have only 1 section)
You can set this after deleting the cell, for example in commitEditingStyle.
Found the solution.
func deleteItem(from indexPath: IndexPath, tableView: UITableView) {
cellsData.remove(at: indexPath.row)
CATransaction.begin()
tableView.beginUpdates()
CATransaction.setCompletionBlock {
tableView.reloadData()
}
tableView.deleteRows(at: [indexPath], with: .left)
tableView.endUpdates()
CATransaction.commit()
}
tableView.reloadData() do the thing that fixing appearing cell above the headerView, but kills animation. So if you don't care about animation you can add only reloadData().

How to avoid flicker when reloading expandable table view cells?

I need the cells of a UITableView to be expanded, but I see a flicker when doing this.
I have this implementation:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Switch row state
expandedFlags[indexPath.row] = !expandedFlags[indexPath.row]
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { () -> Void in
tableView.beginUpdates()
tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
}, completion: nil)
}
Is there any way to avoid the flicking when reloading the cells? I've read several posts but didn't work for my scenario.
tableView.beginUpdates()
....
tableView.endUpdates()
already has its own animation UIView animation what probably makes the flicker
Hi After spending so many hours on this, I finally found the solution to stop this flicker issue in table view while you are expanding or collapsing your tableview cell.
In First step, you have to get your current offset of tableview and then stop the uiview animation and tell your tableview that you are going to update some rows in the tableview. Then pass your current index [IndexPath(row: 0, section: 3)] like this is mine. Your tableview works like charm.
let currentOffset = self.tableView.contentOffset
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 3)], with: .none)
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
self.tableView.setContentOffset(currentOffset, animated: false)

Resources