Reputation: 22926
I'm having an issue with my application architecture...
I have a UITableView
filled with custom UITableViewCells
.
Of course, I use dequeuing so there are only 8-10 cell instances ever generated.
Moving forward to my problem... I have added a theming feature to my application.
When the user long touches the main UINavigationBar
a notification is posted app wide that informs each viewController to update their UI.
When the viewController hosting the tableView with the custom cells receives the notification, it calls tableView.reloadData()
This works well, as I have implemented func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
Inside willDisplay
I call a method on my custom tableViewCell which animates, using UIViewAnimation
, appropriate colour changes to the cell.
It looks like this:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let newsFeedItemCell = cell as? NewsFeedItemCell {
if (self.nightModeEnabled) {
newsFeedItemCell.transitionToNightMode()
} else {
newsFeedItemCell.transitionFromNightMode()
}
}
}
Inside the custom cell implementation those methods look like this:
func transitionToNightMode() {
UIView.animate(withDuration: 1, animations: {
self.backgroundColor = UIColor.black
self.titleTextView.backgroundColor = UIColor.black
self.titleTextView.textColor = UIColor.white
})
}
func transitionFromNightMode() {
UIView.animate(withDuration: 1, animations: {
self.backgroundColor = UIColor.white
self.titleTextView.backgroundColor = UIColor.white
self.titleTextView.textColor = UIColor.black
})
}
This works fine... heres the issue:
Upon scrolling the tableView, any cells that weren't on screen have their colour update/animation code called as they scroll onto the screen, which leads to a jarring user experience.
I understand why this is happening of course, as willDisplay
is only called as cells display.
I can't think of an elegant way to avoid this.
I'm happy that cells on screen are animating for the user experience to be pleasant, however, for cells off screen, I'd rather they skipped the animation.
Possible solutions (though inelegant):
Keep a reference to each of the 8-10 cells created by cellForRow
, and check if they are off screen, if they are set their state immediately.
However, I don't like the idea of keeping a reference to each cell.
Any ideas?
Upvotes: 1
Views: 400
Reputation: 508
I would not use a reloadData
to animate this, since your data model for the tableview is not actually changing.
Instead, I would give the UITableView a function, like so:
class myClass : UITableViewController {
....
func transitionToNightMode() {
for visible in visibleCells {
UIView.animate(withDuration: 1, animations: {
visible.backgroundColor = UIColor.black
visible.titleTextView.backgroundColor = UIColor.black
visible.titleTextView.textColor = UIColor.white
})
}
}
}
and then in your willDisplay or cellForItemAt, set the correct appearance without animation.
Upvotes: 3
Reputation: 19737
I would try the following.
Instead of using UIView.animate
, in transition(To/From)NightMode
I would create a UIViewPropertyAnimator
object that would do the same animation. I would keep the reference to that object around and then in prepare for reuse, if the animation is still running, I would simply finish that animation and reset the state. So something like this:
fileprivate var transitionAnimator: UIViewPropertyAnimator?
fileprivate func finishAnimatorAnimation() {
// if there is a transitionAnimator, immediately finishes it
if let animator = transitionAnimator {
animator.stopAnimation(false)
animator.finishAnimation(at: .end)
transitionAnimator = nil
}
}
override func prepareForReuse() {
super.prepareForReuse()
finishAnimatorAnimation()
}
func transitionToNightMode() {
transitionAnimator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut, animations: {
self.backgroundColor = UIColor.black
self.titleTextView.backgroundColor = UIColor.black
self.titleTextView.textColor = UIColor.white
})
transitionAnimator?.startAnimation()
}
func transitionFromNightMode() {
transitionAnimator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut, animations: {
self.backgroundColor = UIColor.white
self.titleTextView.backgroundColor = UIColor.white
self.titleTextView.textColor = UIColor.black
})
transitionAnimator?.startAnimation()
}
Upvotes: 1