UITableView scrolls choppy when the UITableViewCell is big - iphone

First, I know there are a few answers here on S.O. that addresses the choppy uitableview issue.
Some of them i applied in my code (namely the cell.layer.shouldRasterize = TRUE in particular as well as the cell queue caching thing).
My choppiness is observed to be due to large cell rows (70 pixels height).
If I change the row height to be 20, then it scrolls smooth as butter.
But 30 and above, it gets choppy, especially when i "pull" and "let go" of the table so that it bounces back into place.
One thing that i am NOT doing is flattening the view (I am using the cell's Xib).
Would doing away with the xib design view give me the performance boost that I need?
Also, any ideas why a 30+ pixel height cell row is causing such a drastic difference compared to the smooth-as-butter 20 pixel height?
Note: Even if i make everything in the cell xib to be hidden, i still get choppiness at 70 pixel height.

Check out the example from Apple - TableViewSuite. Look at the fifth example which shows hot to make a very fast cell with images and labels. Also note that cells created using xibs are much slower. So avoid subviews and perform custom drawing in drawRect method. Everything is shown in the apples example:
http://developer.apple.com/library/ios/#samplecode/TableViewSuite/Introduction/Intro.html

Related

What is an acceptable FPS for scrolling, and what are tips for improving performance?

I see in many WWDC video's that says you want to achieve 60.0 FPS as close as possible to get a better smooth scrolling experience. I have a UIScrolLView which loads up image and a couple of table view's at once. Currently I am getting 30 FPS. This is half of what the recommended FPS. Just wondering what FPS do you guys typically get for a table view/scroll view that loads up images and other heavy stuff/rendering stuff.
Any other tips for optiziming FPS? I've spend the past week till now firing up Instruments using the time profiler, allocations, and core animation tool to optimize as much as I can.
Just to clarify a bit on what I have. I have a masonry/waterfall/pinterest style layout on the iPad. So it's not just a regular UITableView. It's a UIScrollView that fills out the whole screen, and is filled with a couple of UIView's. Each of this view has a 150x150 UIImageView and a UITableView and also it has some attributed label, drawn using Core Text. So at one glance when you see the screen, you can see 5-8 table view at one shot, each cell again has a UIImageView and then each cell renders attributed label drawn using core text.
So you can just image how deep and complicated this is. This is not just a regular table view with a UIImageView. I know how to get 60 FPS with just one UITableView in an iPhone with a UIImage. The concept is to load images asynchrounously and not to block the main thread as much as possible.
EDIT:
It seems that the problem here is the UITableView that I have inside my view.. when I remove that from the UIView I get really smooth scrolling..
I uploaded a sample project which is a simpler version of what I have, but it clearly shows the problem. The link is here
Many things affect render performance, here are some items you can check:
Profile - You said you already did this, so great job! Unfortunately profiling is often overlooked, even though it can reveal unexpected problems. In one app I was working on a calendar with different cells representing dates. At first scrolling between cells slow, which was unexpected. I thought maybe it was drawing a few cells too many. After profiling I found that [NSCalender currentCalender] was using 85% of my CPU time! After fixing that everything scrolled great!
Images - Large images put a lot of load in CoreGraphics. Scrolling especially requires a lot of draw operations to move them around. One tip is to scale images on the device as little as you can, that makes CoreGraphics' job a lot easier. If an image is twice as large as the view displaying it, resize the UIImage before displaying it in the view. iOS devices handle PNGs best. They are compressed by a tool (pngcrush) at compile time and iOS has special hardware for rendering them.
Edit: JPGs are probably a better option for photos. iOS devices have dedicated JPG decoders as well.
Custom Drawing - If possible, cutback on the amount of custom CGContext drawing you do. Lots of custom drawing has negative effects on animation speed. I would considering using an image over complex custom drawing, if possible.
Cull - Only draw things you need to. UITableView automatically unloads and loads cells as they appear, so this is done for you, but any custom CGContext drawing should only be done when that part is visible. Also automatic view shadows can be very slow in my experience.
Reuse - Use the reuse identifier on UITableView, this will allow UITableView to reuse cell objects rather than reallocating as it scrolls - look at the answer to this question. Also reuse UIImages rather than allocating multiple for the same file. imageNamed caches images automatically but imageFromContents of file does not.
Create your own - You could create your own grid view class that culls it's subviews views hidden off screen, and scrolls with lazy content loading. By writing a custom solution you can fully control the process and create a design optimized for the usage context. For most use cases you will have a hard time building something better than the Apple standard, but I have seen it done in specific cases.
Last resort - Reduce the size of the offending view (improves filtrate), break content into multiple pages, downsize images, cut out older devices that don't perform as well. I would settle for 30 FPS before sacrificing most of that stuff. Devices will continue to get faster, older devices will be eliminated, and your app will gradually get faster.
I get close to 60 fps with my UITableViewController where the table contains about 2000 cells and each cell pulls an image from the web. The trick is to lazy load the images as you need them. This sample code from Apple is pretty helpful.
The general idea is to keep the UI responsive by not blocking the main thread. Perform downloads and other time-consuming tasks on another thread.
I would do something called Lazy Loading, which doesn't load the images until they are actually seen.
Here's a great example on how to do so: http://www.cocoacontrols.com/platforms/ios/controls/mhlazytableimages
Good Luck!
What I've done is to use NSCache. I've created a small class with properties that conforms to the NSCache data protocol (its really easy to do). So what I do is create a relationship between each cell in the main table and various things worth caching: NSAttributed strings, images etc - really anything that takes work to create. I don't preload it but you could.
When you are asked to provide a cell by the tableview, look in your cache for your primary object. If there, pull all all the objects you need. If the cache does not have the object, then get the data the old fashion way, but before you finish, save it in the cache too.
This really helped me reduce "stutter" when scrolling the cell. Also, do NOT animate anything in the cell - that kills performance. Everything should be fully rendered.
Another thing to remember - make sure ever view which can be set to opaque has its property set to YES. That for sure helps the system render the cell (including the backgound view if you use one.)
EDIT:
So you provided information that included UITableViews may the root problem. So two suggestions:
1) Can you step back and figure out how to make the scrollView a single UITableView? With table headers and footers, and section headers and footers, and even the ability to essentially make a cell a floating view, can't you figure out how to rearchitect what you have?
2) So you decide no to suggestion 1. Then, do this. Think of the space used by the tableview as being a container view. Whenever the tableview is edited, take an image snapshot of it and keep this image around. As soon as the user starts to scroll, swap the tableViews out for the images. When the scrollView stops swap the UITableView back in. This of course will take some fine tuning. In fact, you could probably overlay an opaque image snapshot over the table (which will hide it and prevent it from being asked to draw itself) during scrolling.
the human eye sees at about 60 FPS, so that's why it's recommended, but 30 FPS will also appear very smooth, especially when a regular user is viewing it, as opposed to you trying to find as much to fix as possible. This is obviously dependent on how fast the scrolling goes, if the difference from frame to frame is a movement of a few pixels, 30 FPS will do just fine, but faster movement will require a higher FPS to appear smooth
There are a few things you can do in general to get better table view performance:
1) Switch to Loren Brichter's method of drawing UITableViewCell's (for lack of a better link: http://www.therefinedgeek.com.au/index.php/2010/12/21/fast-scrolling-uitableview-updates-for-ios-4-2/)
Basically, all his code does is render all your cell content as one opaque UIView, which UITableView (and CoreGraphics) can very quickly blast onto a UITableViewCell
If you don't want to do all your cell design in drawRect:, you can still use nibs, but:
Make sure every subview is marked opaque
Don't have any transparent/semi-transparent subviews
Don't have any images with an alpha channel != 1.0f.
2) Don't let UIImageView do any scaling to display your image, give it a correctly-sized UIImage
3) If you're using iOS 5 and above, you can register a nib for a particular cell identifier. That way, when you call [tableView dequeueReusableCellWithIdentifier:], you are guaranteed to get a cell. Cell allocation is faster (according to Apple), and you get to write less code:
- (void)viewDidLoad {
UINib *nib = [UINib nibWithNibName:#"MyCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"MyCellIdentifier"];
}
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"MyCellIdentifier";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Commented out code is no longer needed
//if (cell == nil) {
// cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
//}
// setup cell
return cell;
}
4) Displaying images downloaded from the web
Display a default image (something to look at while the real image is downloading)
Start the download on a separate thread (hint: use GCD's dispatch_async())
When the image comes in, cache it (hint: NSCache), and display it on the cell
Do all image downloading/caching off the main thread; the only thing you should be doing on the main thread is setting the image (remember UI code HAS to be on the main thread!)
You'll probably want to write an async-capable UIImageView (or use an existing library).
Stay away from EGOImageView, even though it has async downloading, it does cache lookup (which happens to be on disk, so that means costly disk IO) on the main thread before dispatching to a background thread for the download. I used to use it, but I ended up writing my own set of classes to handle this, and it's significantly faster.
-
Just follow these, and stuff other ppl have written here, and you'll have table views that scroll like glass in no time :)
You want 60fps, but 30 fps doesn't look too terrible in actuality. But I would try to achieve 60fps for a smoother look while scrolling.
There are many Performance Improvement possibilities, that are also shown by various tutorials
While 60 FPS is the ideal, games like Halo run very prettily in 30 FPS. The battlefield chaos in Halo probably involve more surprising, rapid motion than most lists, even complex ones like yours!

Drawing really long text on iPhone

Currently I have UITableViewCell's that hold sometimes really long text (up to 50,000 pixels in height after drawing). However the UITableView delegate documentation says that cells shouldn't be higher than 2009 pixels in height (any ideas why?).
It's only the first section in my table view that has the really long cell, so instead of using a cell for the first section, I thought I'd create a UIScrollView, put a UITextView as the first "cell" and add it to the scrollView, and then add the tableView to the scroll view as well (under the textView). However, having a 50,000 px high UITextView is causing huge memory problems.
What are my options? I know I could use a UITextView that scrolls, but to have a scrollable UITextView with a tableView just causes complicated scrolling behavior. I want to mimic the scrolling of a tableView.
I didn't know it would be an issue to have a 50,000 px high view in a UIScrollView. I thought that's what UIScrollView's are for? Do I have any alternatives?
I would seriously question the UI design where you must render text that large as part of a table cell. The best option would be to put a reasonably-sized summary in a cell with cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;, build a separate view for the long text, and let the user navigate to that view by clicking the disclosure indicator.
As a side note, you could also put a scroll view inside the initial table cell (not all cells must be of the same type; you can make one with a scroll view in it, and use it for the cell at index zero). It's not going to be as usable as the regular cell with a disclosure indicator, though.

Display lines of data with horizontal and vertical scrolling iOS

I wish to display a dynamic set of data (pulled live over a TCP connection) on the iPhone that is not too unlike lines from a terminal.
This means that the lines will be very long and really cannot be wrapped.
What is the best way of accomplishing this?
I want to use a UITableView because then memory issues would be less of a concern because the lines of text would be loaded/unloaded automatically each line of text would be a cell.
I have thought about this a bit and I came up with a possible solution, but I am not sure if it would actually work.
I could put a UITableView in a UIScrollView and only let the containing UIScrollView scroll horizontally.
Then I would adjust the frame of the UITableView and UITableViewCells so that they are much wider as calculated by the longest line of text.
I have read that you really don't want to nest a UITableView in a UIScrollView since UITableView is in fact a subclass of UIScrollView. Since UITableView is a subclass of UIScrollView, is there someway I can enable the horizontal scrolling and make the frame wider?
If either of those ideas would actually work, could you also tell me which properties I should change (frame or contentSize etc).
I ended up putting a UILabel in a UIScrollView and just kept an array of all the lines and made the UILabel the minimum necessary width and height.
I then set the number of lines of the UILabel to the number of elements in the array. This works because there is no textwrapping.
I think this should be fine performance wise, but we'll see.
You can see it in action in my app Adminium for Bukkit/Minecraft:

How can we horizontally scroll tableview in iPhone SDK?

I want to scrolltableview horizontally as well as vertically. How can I do it?
I'd suggest making a UIScrollView the same size as the screen and then making your UITableView bigger than the screen.Drop the TableView into the ScrollView. Set up the scrollview with a high contenSize.width and then tweak it to work as you desire.
The idea with embedding UITableView into UIScrollView seems to be great, however there is a serious gotcha. UITableView is optimized to display only visible viewpoirt area and limiting allocated UITableView cells. In the case it is embedded into UIScrollView, it allocates complete content area of scroll view, i.e. all rows for the UITableView. It goes out of the memory for approximatelly 2000 rows. Since the UITableView.reloadData creates callback in main thread, it blocks main thread to respond to didReceiveMemoryWarning and application is killed on system sole discretion for level 2 warning, which is never received.
It seems that the better way is in subclassing UITableView and extending rows to width which can be scrolled horizontally.
If you are looking for something similar to what is done in 'Pulse', then this tutorial might help you:
http://iosstuff.wordpress.com/2011/06/29/creating-pulse-style-scrolling-horizontally-scrolling-uitableview-as-a-subview-of-uitableviewcell/
It shows how to add a horizontally scrolling UITableView to a vertically scrolling UITableView

UITableViewCell with custom disclosure causes mis-alignment?

I hope someone reading this might be able to shed some light on this problem.
I have a plain UITableView containing custom UITableViewCells, each cell is a different height provided by tableView:heightForRowAtIndexPath: and the table does not scroll. The cells themselves are really containers for one or two UILabels nothing very special. Three of the cells show a custom disclosure when editing apart from that no disclosures are used for anything else. The custom disclosures are UIImageView's assigned to the cells editingAccessoryView with 28x29 images however the UIImageView size is set to 40x29 to give a better position using contentMode Left.
This should work fine but what happens is when the disclosures appear in edit mode they don't all share the same position - two might be the same x and the third might be 5px different. This seems to be dependant on the height given to each cell, they basically move around. I can't see how they are associated but have found one set of heights that give the expected results of all three being in the same position. This isnt ideal but has been working fine till now - I have no choice but to change the heights and so the alignment is a big problem.
If I remove the custom disclosures then the standard disclosures work perfectly?
I've tried lot's of things, i.e.
removing the extra width on the UIImageView
reducing the size of the image
playing around with the cell heights - the disclosures just move around
replacing the UIImageView's with other controls - no difference
If you have any ideas why this might be I'd love to here them, thanks.
My guess is its because you are using content mode left in the UIImageView. Use content mode center.
The views get stretched in all directions when the height changes, but the image stays on the left edge of the view...