reloadData() of UITableView with Dynamic cell heights causes jumpy scrolling
Solution 1
To prevent jumping you should save heights of cells when they loads and give exact value in tableView:estimatedHeightForRowAtIndexPath
:
Swift:
var cellHeights = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? UITableView.automaticDimension
}
Objective C:
// declare cellHeightsDictionary
NSMutableDictionary *cellHeightsDictionary = @{}.mutableCopy;
// declare table dynamic row height and create correct constraints in cells
tableView.rowHeight = UITableViewAutomaticDimension;
// save height
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[cellHeightsDictionary setObject:@(cell.frame.size.height) forKey:indexPath];
}
// give exact height value
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
if (height) return height.doubleValue;
return UITableViewAutomaticDimension;
}
Solution 2
Swift 3 version of accepted answer.
var cellHeights: [IndexPath : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 70.0
}
Solution 3
The jump is because of a bad estimated height. The more the estimatedRowHeight differs from the actual height the more the table may jump when it is reloaded especially the further down it has been scrolled. This is because the table's estimated size radically differs from its actual size, forcing the table to adjust its content size and offset.
So the estimated height shouldn't be a random value but close to what you think the height is going to be. I have also experienced when i set UITableViewAutomaticDimension
if your cells are same type then
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100//close to your cell height
}
if you have variety of cells in different sections then I think the better place is
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//return different sizes for different cells if you need to
return 100
}
Solution 4
@Igor answer is working fine in this case, Swift-4
code of it.
// declaration & initialization
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
in following methods of UITableViewDelegate
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// print("Cell height: \(cell.frame.size.height)")
self.cellHeightsDictionary[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.cellHeightsDictionary[indexPath] {
return height
}
return UITableView.automaticDimension
}
Solution 5
I have tried all the workarounds above, but nothing worked.
After spending hours and going through all the possible frustrations, figured out a way to fix this. This solution is a life savior! Worked like a charm!
Swift 4
let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)
I added it as an extension, to make the code look cleaner and avoid writing all these lines every time I want to reload.
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
beginUpdates()
endUpdates()
layer.removeAllAnimations()
setContentOffset(lastScrollOffset, animated: false)
}
}
finally ..
tableView.reloadWithoutAnimation()
OR you could actually add these line in your UITableViewCell
awakeFromNib()
method
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
and do normal reloadData()
Related videos on Youtube
David
Updated on August 04, 2021Comments
-
David over 2 years
I feel like this might be a common issue and was wondering if there was any common solution to it.
Basically, my UITableView has dynamic cell heights for every cell. If I am not at the top of the UITableView and I
tableView.reloadData()
, scrolling up becomes jumpy.I believe this is due to the fact that because I reloaded data, as I'm scrolling up, the UITableView is recalculating the height for each cell coming into visibility. How do I mitigate that, or how do I only reloadData from a certain IndexPath to the end of the UITableView?
Further, when I do manage to scroll all the way to the top, I can scroll back down and then up, no problem with no jumping. This is most likely because the UITableViewCell heights were already calculated.
-
Lyndsey Scott about 9 yearsA couple things... (1) Yes you can definitely reload certain rows using
reloadRowsAtIndexPaths
. But (2) what do you mean by "jumpy" and (3) have you set an estimated row height? (Just trying to figure out if there's a better solution that would allow you to update the table dynamically.) -
David about 9 years@LyndseyScott, yes, I have set an estimated row height. By jumpy I mean that as I scroll up, the rows are shifting upwards. I believe this is because I set an estimated row height of 128, and then as I scroll up, all my posts above in the UITableView are smaller, so it shrinks the height, causing my table to jump. I'm thinking of doing reloadRowsAtIndexPaths from row
x
to the last row in my TableView... but because I'm inserting new rows, it won't work, I can't know what the end of my tableview will be before I reloaded the data. -
Hot Licks about 9 yearshow do I only reloadData from a certain IndexPath to the end of the UITableView? -- You do that by reading the documentation.
-
Lyndsey Scott about 9 yearsNote for future answer seekers: It seems as if the issue comes from using
tableview.rowHeight = UITableViewAutomaticDimension
. The issue was also mentioned in this answer: stackoverflow.com/q/25999880/2274694 -
rad about 9 years@LyndseyScott still i can't solve problem, is there any good solution?
-
user3344977 about 9 yearsDid you ever find a solution for this problem? I am experiencing the exact same problem as seen in your video.
-
z22 over 8 yearsDid anyone find the solution to this issue? I am too facing this issue shown in the video
-
Jonny over 6 yearsI was using exact cell heights but was supposedly hit by this, or a very similar problem. Setting the exact size as estimated size (as suggested in several answers, but I could do it directly in my storyboard) fixed that problem - the table view would jump around a bit strangely after deleting some source data and then doing
reloadData()
. -
Srujan Simha almost 6 yearsNone of the answers below worked for me.
-
tboyce12 over 5 yearsCan you please re-upload the video? Link appears broken.
-
mfaani over 5 yearsThere seems to be a bug for when you use
UITableViewAutomaticDimension
. See this answer and other answers to the question. @SrujanSimha
-
-
David about 9 yearsI've tried the beginUpdates/endUpdates method, but that only affects the visible rows of my table. I still have the issue when I scroll up.
-
Lyndsey Scott about 9 years@David Probably because you're using estimated row heights.
-
David about 9 yearsShould I get rid of my EstimatedRowHeights, and instead replace it with the beginUpdates and endUpdates?
-
Lyndsey Scott about 9 years@David You wouldn't be "replacing" anything, but it really depends on the desired behavior... If you want to use estimated rows height and just reload the indexes below the current visible portion of the table, you can do that like I said using reloadRowsAtIndexPaths
-
David about 9 yearsOne of my issues with trying the reladRowsAtIndexPaths method is that I'm implementing infinite scrolling, so when I'm reloadingData it is becauseI just added 15 more rows to the dataSource. This means that the indexPaths for those rows don't yet exist in the UITableView
-
Lyndsey Scott about 9 years@David Oh well if you're inserting rows, why not use
insertRowsAtIndexPaths:
instead -
David about 9 yearsInteresting. I suppose I'm too used to Android Dev where you just notify the list of changes and it does the rest. I'm still a bit perplexed with how I would go about inserting rows to the end of the table. Would I have to create 15 indexPaths and give each their own row number if I'm going to insert 15 items to the end of the UITableView?
-
Lyndsey Scott about 9 years@David Yeah, you could create an array of the 15 index paths.
-
David about 9 yearsI was under the impression IndexPaths were for the UITableView to handle and fetch from, not to create on ones own. I see. So even if my UITableView currently only had 30 items, and I'm appending 15, I'd use indexPaths with row 30 - 44 (since its 0-indexed)?
-
Lyndsey Scott about 9 years@David I'd recommend trying it yourself and seeing if it work for your particular case.
-
David about 9 yearsI'm using the insert method, and the rows are being properly inserted, but the problem still persists and for some reason the heights for the rows not above the screen (not visible) are still being recalculated and thus causing the jumpy behavior
-
Lyndsey Scott about 9 years@David And, just curious, if you remove the estimated row height does the issue persist?
-
David about 9 yearsYeah, it still persists. I included a video in the Question above so you can visually see the issue.
-
Lyndsey Scott about 9 years@David Oh, I see. It looks like your table height of the elements changes below the visible table thus shrinking the content size of the scrollview.
-
David about 9 yearsIf it changes below the visible table, wouldn't it just change once and I'd see one bump. Instead, it seems like it continues to be changed (if that is the case)
-
Lyndsey Scott about 9 years@David, depends on how often you're reloading/inserting?
-
Lyndsey Scott about 9 yearsLet us continue this discussion in chat.
-
Flappy almost 8 yearsFixed it wen overriding the estimatedHeightForRowAtIndexPath method with an high value, for example 300f
-
Artem Z. over 7 yearsThanks, u really save my day :) Works in objc too
-
Gerharbo over 7 yearsDon't forget to initialize
cellHeightsDictionary
:cellHeightsDictionary = [NSMutableDictionary dictionary];
-
Natalia about 7 yearsThanks this worked great! in fact I was able to remove my implementation of
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
, this handles all the height calculation I need. -
liushuaikobe almost 7 years
estimatedHeightForRowAtIndexPath:
returns a double value may cause a*** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:]
error. To fix it,return floorf(height.floatValue);
instead. -
Madhuri almost 7 yearsHi @lgor, I'm having the same issue & trying to implement your solution. The issue i'm getting is estimatedHeightForRowAtIndexPath gets called prior to willDisplayCell, so cell's height is not calculated when estimatedHeightForRowAtIndexPath is called. Any Help?
-
Donnit almost 7 years@Madhuri effective heights should be calculated in "heightForRowAtIndexPath", that is called for every cell on the screen just before willDisplayCell, which will set the height in the dictionary for later use in estimatedRowHeight (on table reload).
-
Igor almost 7 years@Madhuri, this solution for "Dynamic cell heights". First time height of cell defined by autolayout. Check your constraints and set
tableView.rowHeight = UITableViewAutomaticDimension;
-
MJQZ1347 over 6 yearsAfter struggling many hours with persistant jumping I figured out that I forgot adding
UITableViewDelegate
to my class. Conforming to that protocol is neccessary because it contains the above shownwillDisplay
function. I hope I can save someone the same struggle. -
Alexey Chekanov almost 6 yearsHow to deal with row insertion/deletion using this solution? TableView jumps, as the dictionary data isn't actual.
-
Louis de Decker almost 6 yearsthank you, it's exactly why my tableView was so jumpy.
-
tryKuldeepTanwar almost 6 yearsI've always underestimated estimatedHeightForRowAtIndexPath before the day i shaw you answer... wow man works like a charm.
-
Gaganpreet over 5 yearsHello @Igor i'm having same tableview jumping problem after reload every time, i'm trying many solutions to fix it but same problem is there any solution.
-
mfaani over 5 yearsHow should you deal with row insertion/deletion using this solution? TableView jumps, as the dictionary data isn't actual.
-
matt over 5 yearsHow does this do any reloading? You call it
reloadWithoutAnimation
but where's thereload
part? -
rgreso over 5 yearsFor me this worked with combination of setting estimatedRowHeight, estimatedSectionHeaderHeight, estimatedSectionFooterHeight to 1.
-
Srujan Simha over 5 years@matt you could call
tableView.reloadData()
first and thentableView.reloadWithoutAnimation()
, it still works. -
Vitalii over 5 yearsAn old answer, but it is still actual as of 2018. Unlike all other answers, this one suggests setting estimatedRowHeigh once in viewDidLoad, which helps when cells are of same or very similar height. Thanx. BTW, alternatively esimatedRowHeight can be set via Interface Builder in Size Inspector > Table View > Estimate.
-
T.Y. Kucuk over 5 yearsGreat! None of above didn't work for me neither. Even all heights and estimated heights are totally the same. Interesting.
-
Ning over 5 yearsworks great! especially on the last cell when reload row.
-
Trev14 about 5 yearsThank you for the Swift answer. In my case I was having some SUPER weird behavior of cells going out of order on reload when the table view was scrolled to/near the bottom. I'll be using this from now on whenever I have self-sizing cells.
-
Kakashi about 5 yearsDon't work for me. It is crash at tableView.endUpdates(). Can someone help me!
-
Rohan Sanap about 5 years@Flappy it is interesting how solution provided by you works and is shorter than other suggested techniques. Do consider posting it as an answer.
-
Adam S. almost 5 yearsWorks perfectly in Swift 4.2
-
BennyTheNerd almost 5 yearsThe key for me was implementing his UITableView extension here. Very clever. Thanks rastislv
-
Soufian Hossam almost 5 yearsWorks perfectly but it has only one drawback, you lose the animation when inserting header, footer or row.
-
Luke Irvin almost 5 yearsWhere would reloadSectionWithouAnimation be called? So for example, users can post an image in my app (like Instagram); I can get the images to resize, but in most cases I have to scroll the table cell off scree for that to happen. I want the cell to be the correct size once the table goes through reloadData.
-
henrique almost 5 yearsWorks like a charm. Do you have any idea why this happens? I'd suggest that is due to the
UITableView
algorithm used to find the height for cell given an estimated height "considerably far" from the correct/desired/necessary height. -
Igor almost 5 yearsyeah, I guess the same way
-
Philip Borbon over 4 yearsA life saver. So helpful when trying to add more items in the datasource. Prevents jumping of newly added cells to the center of the screen.
-
nteissler over 4 yearsprovided a more accurate estimated height helped me. I also had a multi-section grouped table view style, and had to implement
tableView(_:estimatedHeightForHeaderInSection:)
-
Zack about 4 yearsThis is a great answer - my only suggestion would be to replace your default value in the
estimatedHeightForRowAt:
method withUITableView.automaticDimension
. This way it will fallback to the (often imprecise but hopefully close) automatically determined value from Apple rather than 70. -
Raj D almost 4 yearsWOW. Such little concept can made a huge difference! I wonder if there's something for collectionview too
-
Legonaftik over 3 yearsWhat if your screen has, let's say, pull-to-refresh and you got new elements inserted at the top? At this moment
[IndexPath: CGFloat]
would be invalidated. -
Yahia over 3 yearsThat worked for me, but the question is Why?
-
ekashking almost 2 yearsThat actually works. However, the question now is: how come
estimatedHeightForRowAtIndexPath
but the regularheightForRowAtIndexPath
doesn't??? -
Igor almost 2 yearswith dynamic cell height
estimatedHeightForRowAtIndexPath
used for calculate cells size on scrolling