Autolayout self-sizing cells and UITableView stuttering/jiggling - ios

Learning efficient ways of calculating the height of dynamic tableview cells with autolayout. Faced the problem when scrolling table view, it begins to stutter a little bit, which is very annoying. Sometimes it stutters a lot when scrolling up, but that is probably related to my demo core data code.
This video demonstrates the problem. The sample project is on github repository (see SZNewsFeedViewController).
The layout of the cell is very simple:
My first guess was looking at blended layers in the simulator, but it's fine:
Stuttering does not go away if I comment out images setting, so, that is probably not the real reason. I set the images asynchronously with FastImageCache. I think I could watch for active scrolling and skip images setting until it stops completely, but I'm not sure if this could really help.
Replacing tableView:heightForRowAtIndexPath: code with return 250 does not solve the issue completely too.
Right now I change the height constraint constant for posts with images, will try a separate cells for plain text posts and images posts.
Can you point me out, what may cause such a stuttering and what can i try more to solve it? What's wrong with my layout or height calculating code?
Is there any appropriate way to pre-calculate heights (with the same values as systemLayoutSizeFittingSize returns) in the background thread and save it to core data model, to be able to reuse those values fast?
EDIT: the profiler say that height calculating takes most of the time:
UPD:
Ok, seems like a separate cells + height caching is a better solution - tableview jiggles less for a little bit. Also, tableView:estimatedHeightForRowAtIndexPath: seems to be more harmful then helpful. Removing this method removes jiggling a little bit more too. I figured out that after the approaches above, [collectionview reloadData] call affects most of all - removing this call leads to almost no jiggling. I will try static imageviews + watching the active scrolling to determine if this wouldn't lead to jiggling.

It looks like you're still implementing heightForRow:atIndexPath: and not utilizing UITableViewAutomaticDimension.

Related

How to improve UILabel performance in a table view?

I have a table view with custom cells and I am trying to optimize it to make the scrolling as smooth as possible. I've followed most of the advises about table view optimization, for example, using fixed heights, not using AutoLayout etc.
I have achieved in a very optimized state except one thing. The custom cell has one image and several UILabels. With one image & 3 labels, the performance is perfect. However, when I add the 4th label, I start to observe a bit of jerkiness, very small but not as smooth as before.
I have searched the net and found CoreText can help performance. Is CoreText a right direction? Are there any other things that can help to improve UILabel performance when used in a table view?
EDIT:
I only use simple functions for UILabel by setting its font, font color, text and numberOfLines.
Core text is probably the ultimate solution for performance, but even before getting there I like to use a program called PaintCode to draw up the contents of my cells if they are particularly complicated.
Other things you can do however to speed things up a bit before you need to get to that point:
Do all calculations before cellForRow is called. So any number formatting, date calculations etc, should all be done and stored on your model object beforehand. That way you can just plug them straight in to your cells properties and they're ready to be displayed. Any extra time spent in cellForRow will slow down your tableview.
Set all labels and views to opaque. You'll probably have to set their background color when you do this, but it makes drawing the views that much faster. You can see which views have transparency applied by turning on "Color Blended Layers" from the debug menu for the simulator. What you want is to have your cells completely green.
Keep some dummy text in all labels by default. Updating text would not cost memory.
CoreText is pretty expensive and probably not meant for this! I think dummy text tricks should work for you.
Hope this works.

UICollectionView resizes cells incorrectly after reloadData

I have a UICollectionView that renders some autosizing cells. When I load the data into the collection view initially, the data is perfectly sized the way it should be.
I have a weird issue where, when I trigger reloadData with a new data set, the auto sizing goes wrong and I end up with incorrectly sized cells.
Images attached.
I have tried invalidating the collectionViewLayout before I call reloadData.
Oddly enough, if I remove data, and trigger a reloadData the cells resize to their correct layout. So I am wondering if this is a view recycling issue?
Thanks in advance.
Below is the layout after the screen was initially loaded for the first time.
Below is the layout after the + opens a modal to add a new post, closes, and refreshes the data - see incorrect sizing of cell "Thanks for all your hard work".
Below is the screen after the screen was reloaded from scratch again.
My answer might be a bit late, but getting the similar behaviour I did manage to solve it.
In my case, the problem was with multiline labels — so changing them to single-line solved the case.
As I didn't need to have multilines — I had no further research on them.
I hope, this answer could be a good starting point to find a fix.

Resizing UITableViewCell to fit Content Performance

I am trying to create a table to show posts by users with possibly variable lengths. I want each cell in my table to resize to fit the content of the UILabel containing the text of the post. Currently I am doing this with auto-layout, programmatically setting constraints and calculating the height of the table view cell based on the height of the UILabel. Everything looks as I want it to, except when rapidly scrolling through the table the performance is horrendous, with the CPU getting fully maxed out and unable to keep up with setting the constraints for each cell as they are reused and placed with new text.
I was wondering if anyone knows of a better way to do this. Is there a way I can continue to use auto layout to size my cells without sacrificing performance? Or would the best solution be to create different sized cells with different identifiers, and just choose the best fit based on the UILabel text size.
I also read somewhere that UICollectionView has much better performance in displaying variable-sized content, would it be worthwhile/possible to try configuring a UICollectionView to display the messages, and somehow make it look like a UITableView?
Essentially I just need a suggestion on how to display messages of variable sizes (up to about 7 or 8 lines of text max) in a TableView-like manner without causing massive slowdowns as the user rapidly scrolls through the table.
Thank you
The best trick in the book for things like this is cacheing. Add to your model anything that's expensive to compute (not the views themselves, but the computational results, like view bounds sizes). Make your datasource methods all about looking up and assigning, not at all about computing.
The way I had implemented adding the constraints, the constraints were updated whenever [self updateViewConstraints] was called, regardless of whether or not the cell had already had its constraints calculated and applied. I resolved this by adding a BOOL property to my custom TableViewCell called didUpdateConstraints, and setting it to YES as soon as constraints are updated the first time. Then in my [self updateViewConstraints] method I only update the constraints if !self.didUpdateConstraints
Now that constraints are not being needlessly updated when all views have already been correctly constrained, the performance is significantly better, and I don't observe any slowdown upon scrolling through the table.

How can you create a zoomable timeline in iOS?

I would like to create a zoomable timeline in my iOS application for a kind of a todo-list. Zooming in would display days and hours and zooming out would trigger the folding out of days or zooming out to months. There would be a scrolling function.
As an example, I would want it to work like this: http://almende.github.com/chap-links-library/js/timeline/doc/
What kind of basic view would be an appropriate starting point for this, keeping in mind that the memory needed should be as low as possible? Would a UITableView, UIScrollView, or something else work for this?
The UICollectionView / UITableView will not work because the cells are almost always the same width/height. Most importantly the cells always have the same spacing in-between each cell. Because of this it is able to easily calculate what the index range is, and query the dataSource for the cell's it needs based on index.
A timeline view on the other hand is much different than these controls. The spacing between cells is different, with the cells sometimes overlapping with each other. If you had a data source sorted by position, the control would still have to guess where to start looking for the range. So finding the correct index range is going to be more expensive - you just have to find the right algoritm to determine this in a shorter amount of time.
You're going to have to build your own control by subclassing a UIScrollView. You shouldn't have to mess with drawRect at all. An important concept, which is used by UITableView and UICollectionView, is dequeue'ing cells. The iOS 5 version of Apple's PhotoScroller demonstrates this concept with paging (the iOS 6 version replaces the custom paging with UIPageViewController). You'll have to download the old documentation to get the old sample code.
I'm currently building a timeline view, which I will open source at some point. It's somewhat based on the UITableView and works in horizontal or vertical direction. It dequeue's cells just like the UITableView. I'm not focusing on labels or scaling, but the concept of having inconsistent spacing in-between cells. To give you a head start, here are my dataSource methods I settled on:
- (NSInteger)numberOfCellsInTimelineView:(TimelineView *)timelineView;
- (CGRect)timelineView:(TimelineView *)timelineView cellFrameForIndex:(NSInteger)index;
- (TimelineViewCell *)timelineView:(TimelineView *)timelineView cellForIndex:(NSInteger)index;
Two of these calls are identical to what UITableView has, but it has a new call called cellFrameForIndex. What's significant about this call is that the TimelineView can guess an index and lookup the frame of the cell and see where it fits in the visible bounds. If it guesses a cell inside the visible bounds, it can simply iterate forward and backward until it finds the edges.
Currently the algoritm I'm using takes round(count * (CGRectGetMidX(timelineView.bounds) / timelineView.contentSize.width)) (for the horizontal direction). Basically what this does is takes the mid-point of the visible bounds of the UIScrollView and gets the percentage of what has been scrolled. Then it multiplies that by the number of cells. This works fairly well. When testing a random data-set with 100,000 records at random spacing the calls to cellFrameForIndex ranged from 8 to 150. I'm able to pull 52-60 FPS with this. I'm still working on it, and trying to find a better/quicker way to determine the index range. I'm trying to keep it down to visible cell count + 10 (max) iterations.
If you have time to wait, I'll update my answer to include a link to my GitHub project when I'm done. This could be a few days, to a week. I may add scaling. But you'll have to fork it and add labels and anything else you want.
Edit:
Here is my Github project: https://github.com/lukescott/TimelineView
UITableView is definitely not suitable. UIScrollView might be better but it is not very well suited for dynamic or very long content.
I believe the easist approach would be to do it everything by yourself - implement it by a UIView subclass with a drawRect. Of course, you should use UIPanRecognizer in the same manner as UIScrollView uses it.

UITableView with floating cells

I am working on a specific type of list for an iOS app, where cells would simply have a specific width and height and then float left (think of something like CSS float:left), creating then a list that would scroll if more than 12 items (like the picture bellow).
I was thinking that perhaps this could be done by customizing UITableViewCell and setting the frame's width and height, so I could re-use the various benefits UITableViewController, delegate and dataSource offer. but unfortunately it seems that's not possible.
So, I'm now simply creating many UIViews and positioning them across its parent programatically.
I just would like to make sure this would be the right approach to achieve such kind of lists and if someone could let me know if it's indeed not possible to have this kind of functionality on UITableView and inherit all the goodness it offers.
Thanks guys
I know this question is already answered, but still...
I believe you can simply use UICollectionView since iOS 6.0.
Documentation
And this is how it looks like (image)
Maybe this will help somebody.
UITableView can only have cells that go in one direction. It's perfectly okay to create many UIViews (table view cells are also UIViews), if the number of them is reasonable. You can usually have a few hundred UIViews in a scrollview without seeing any performance issues, but that also depends on the complexity of what's inside the uiviews. Try it for yourself and if you find out, that you need too many uiviews, or that their complexity it too high, you can optimize further. You can set the .layer.shouldRasterize property to "cache" the content of the views. Or you can use CATiledLayer, which is something like a UITableView, but it supports tiles in all direction (and scrolling, zooming, ...).
Take a look at this project, I think may be helpful to for your problem
link

Resources