i am trying to use scrollToRow but when call this method tableview does not show any data sometimes. When i check UI inspector i can see table cells but does not seen on screen.
I tried to DispactQueue didn't solve this problem
var forIndex = 0
for item in filteredData {
if let firstCharacter = item.Name?.prefix(1), firstCharacter == char {
let indexpath = IndexPath(row: forIndex, section: 0)
self.tableView.reloadRows(at: [indexpath], with: UITableView.RowAnimation.top)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute: { [weak self] in
self?.tableView.scrollToRow(at: indexpath, at: UITableView.ScrollPosition.top, animated: true)
})
break
}
forIndex += 1
}
char is element of list like a,b,c,d... FilteredData is list of tableview elements
When debugging if scrollToRow method marked breakpoint it's working
Probably the reloadRows (at:)
animation is still in progress when you kick of the next causing this strange behavior.
With the breakpoint, the reloadRows (at:)
has a chance to finish and on continuing
the scrollToRow
kicks off after.
Try one of the following if this helps your situation assuming there is no issue with your data source:
1. Change
tableView.reloadRows(at: [indexpath], with: .top)
to
tableView.reloadRows(at: [indexpath], with: .none)
so that the UITableView applies a default animation
OR
2. Increase your delay before kicking off the next animation
OR
3. Perform the reload without animation
UIView.performWithoutAnimation { [weak self] in
self?.tableView.reloadRows(at: [indexpath], with: .none)
}
tableView.scrollToRow(at: indexpath,
at: UITableView.ScrollPosition.top,
animated: true)
OR
4. Use beginUpdates
and endUpdates
which can be useful when performing batch operations on table views
tableView.beginUpdates()
tableView.reloadRows(at: [indexpath], with: .top)
tableView.scrollToRow(at: indexpath,
at: UITableView.ScrollPosition.top,
animated: true)
tableView.endUpdates()
Do any of these give you better results?
Update
Looking more at the docs for reloadRows(at indexPaths:
, here are some lines that stood out for me
Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.
So it seems that in some situations animation might not be needed as the cell could be off screen or not needed, so the simplest way to change data is get the cell at the index path and change the data without this function.
Since you are running this in a loop, you most likely start the next indexPath's reloadRows
before the previous indexPath's scrollToRow
is complete and this can cause some unusual behavior.
Since UITableView does not have it's own completion handler, we can try using CoreAnimation with recursion which can be option 5.
Here is small example I prepared. My sample is like this:
tableView.reloadRows(at:
tableView.scrollToRow(at:
Here is how I accomplish that
First I extend the UITableView to use CoreAnimation block to do reloadRows
and scrollToRow
extension UITableView
{
func reloadRows(at indexPaths: [IndexPath],
with animation: UITableView.RowAnimation,
completion: (() -> Void)?)
{
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
reloadRows(at: indexPaths, with: animation)
CATransaction.commit()
}
func scrollToRow(at indexPath: IndexPath,
at scrollPosition: UITableView.ScrollPosition,
animated: Bool,
completion: (() -> Void)?)
{
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
CATransaction.setAnimationDuration(2) // set what you want
scrollToRow(at: indexPath, at: scrollPosition, animated: animated)
CATransaction.commit()
}
}
Here is how I use this extension with my view controller set up
class TableViewAnimation: UITableViewController
{
let numberOfRows = 15
// Change the color of these rows in tableview
var colorChangeArray: [Int] = []
// Copy of colorChangeArray used in recursion
var colorChangeQueue: [Int] = []
// Change the color of row to this
private let colorToChange = UIColor.systemBlue
// Normal cell color
private let colorNormal = UIColor.gray
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .white
setUpNavigationBar()
setUpTableView()
}
private func setUpNavigationBar()
{
title = "Table View Animate"
let barButton = UIBarButtonItem(title: "Animate",
style: .plain,
target: self,
action: #selector(didTapAnimateButton))
navigationItem.rightBarButtonItem = barButton
}
private func setUpTableView()
{
tableView.register(CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier)
}
@objc
func didTapAnimateButton()
{
// Queue all the rows that should change
// We will dequeue these in the animation function
// and the recursive function stops when the queue
// is empty
colorChangeQueue = [1, 3, 6, 12]
resumeAnimation()
}
// Recursion function rather than loops using our
// custom functions from extension
private func resumeAnimation()
{
if !colorChangeQueue.isEmpty
{
let rowToChange = colorChangeQueue.removeFirst()
print("starting \(rowToChange) animation")
let indexPath = IndexPath(row: rowToChange,
section: 0)
colorChangeArray.append(rowToChange)
tableView.reloadRows(at: [indexPath],
with: .top) { [weak self] in
self?.tableView.scrollToRow(at: indexPath,
at: .top,
animated: true,
completion: {
// recursively call the function again with a small delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
{
print("complete \(rowToChange) animation")
self?.resumeAnimation()
}
})
}
}
}
}
Finally, here is the data source and delegate but nothing unique is happening here, just adding it for completeness
extension TableViewAnimation
{
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
return numberOfRows
}
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell
= tableView.dequeueReusableCell(withIdentifier: CustomCell.identifier) as! CustomCell
if colorChangeArray.contains(indexPath.row)
{
cell.mainView.backgroundColor = colorToChange
}
else
{
cell.mainView.backgroundColor = colorNormal
}
cell.textLabel?.textAlignment = .center
cell.textLabel?.text = "Row \(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 150
}
}
Now it seems like the animations of each of the row is sequenced properly:
And also if you see the print in the console, it is sequenced which did not happen with the loop method:
starting 1 animation
complete 1 animation
starting 3 animation
complete 3 animation
starting 6 animation
complete 6 animation
starting 12 animation
complete 12 animation
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.