How to avoid flicker when reloading expandable table view cells? - ios

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)

Related

Add one cell upon drag in UITableView

I am trying to make a table view that can add a cell when it's dragged downwards up to a certain content offset. Something like the animations in the following links:
Animation1
Animation2
I have tried using the delegate function: scrollViewDidScroll(_:) but I'll need to stop the function once the cell is added.
My Code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y <= -110 {
cellCount += 1
dataSource?.append(Model())
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
}
How to achieve the animations in the links?
Check this Github Repo FlippingNotch
quickbirdstudios has shown how to make first animation

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
}

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().

moveRow(at:to:) doesn't work if the UITableView has many items

I'm trying to animate move items between sections after selecting row.
This is code I'm using for moving items from section 0 to section 1:
tableSource[indexPath.section].tableItems.remove(at: indexPath.row)
tableSource[1].tableItems.insert(data, at: 0)
tableView.moveRow(at: indexPath, to: IndexPath(row: 0, section: 1))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 1)], with: UITableViewRowAnimation.automatic)
})
DispatchQueue is used to update row after animation.
It works perfectly if all items fit in the screen or if section 1 is visible. Otherwise, I get the behavior as when you delete a row and add it to another index.
It's because UITableView doesn't know where to animate if cell isn't visible

ReloadRowsAtIndexPaths with no Row Animation animates

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.

Resources