Best Practices: Fast UITableView Scrolling? - iphone

I know there has been a lot of discussion around fast scrolling. Apple provides excellent examples:
http://developer.apple.com/library/ios/#samplecode/TableViewSuite/Introduction/Intro.html
I will have a custom UITableViewCell that has 2 UIImageView's and 3 UILabels as subviews. If I just add them as subviews to the content view will my scrolling be slow?
Would it be best to just draw these?

The answer I got from the UIKit guys at WWDC this year was "it kinda depends on what you're doing". If everything in the cell is opaque and simple, adding subviews directly to the content view is very performant. If you need some views to have varying opacity or changes, rendering it manually can be faster.
The videos for WWDC were just posted, I recommend you watch session 105 (second half specifically), it addresses analyzing and improving performance in UITableView, in a real world app.

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!

Custom tab bars, table views, etc

this is my first question here at Stack Overflow.
I've been looking for a way to do this ever since I started programming for iOS (which was really not that long ago, about a month) but I haven't been able to find any concrete explanation, so I decided to ask you.
I've seen many apps that have custom artwork in them (e.g. wooden textures, backgrounds with noise, custom table view cells, etc.) and I would like to add some to my own apps, are there any properties that I can access, say for example, on a tab bar?
Check out Wunderlist or ReadMore, these two are great examples of what I mean.
Wunderlist was made using Titanium, obviously editing backgrounds and customizing table view cells and table view backgrounds is easier in JavaScript, but if Titanium has a way of doing it then there must be a way of doing it natively in Objective-C, right?
Thank you in advance, your help is very much appreciated.
There are a number of ways to create a custom UITableView and associated cells.
As a general overview, the Cocoa with Love Easy custom UITableView drawing blog post is pretty good and there's also a more recent (and complex) UITableView construction, drawing and management (revisited) post on the same site if you really want to go for it on the customisation front.
there a few ways of customizing UITabelViewCells.
Table View Programming Guide for iOS
Interessting Chapters:
Programmatically Adding Subviews to a Cell’s Content View
Loading Custom Table-View Cells From Nib Files
Subclassing UITableViewCell

UITableViewController custom cell scrolling performance

So I have a UITableViewController. The cells in this tableview have the following UIControls:
2 UILabels, one of which has a shadow and a clearColor background color.
1 Custom Progress view resized to be larger and with a different color
3 UIButtons
Functionally, they do exactly what they are supposed to do. However, I've noticed when looking at it on device that scrolling performance quickly tanks and has dropped frames all over the place, even with other interactions like pushing one of the buttons.
So I was reading around today and found http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/ this article by the Tweetie guy about how to achieve fast scrolling performance by subclassing UITableViewCell and doing the drawing yourself.
The example works extremely well, but when I tried to adapt it to work with my desired configuration I realized that he isn't using any predefined UI Controls, he's mapping out everything by hand.
While I can see how this would be an extremely efficient way to do things, it strikes me as problematic for things like the progress view and the buttons, and even one of my labels to a certain extent.
So my question is this: Do I need to completely write my controls from scratch if I want my scrolling performance to be good, or is there a way to use the standard UI Controls and get good scrolling performance?
If you're adding custom controls to your cell, you should still be subclassing UITableViewCell, adding your controls in the init function, laying them out in layoutSubviews, etc. - just like any other view. As VdesmedT says, make sure you're re-using cells via the dequeue mechanism, so that you aren't allocating new cells with each scrolling operation.
OK, I will propose something obvious but to achieve UIScrollView performance, you need to be sure that the dequeue mechanism works well. I often see developers not properly set the identifier in IB and therefore missing the UITableViewCell cache benefit

Lazy loading of subViews into a non-paging UIScrollView

I am trying to implement a filmstrip-like UIScrollView that will be populated with thumbnails of catalog pages. Selecting a thumbnail image will cause the main UIScrollView to move to the selected page. The Catalog may contain 100 - 200 pages, and I want to load them lazily only when required.
I have done this in a UIScrollView with paging enabled, but haven't seen anything on the best way to do this in a non-paging scenario. There will be 6 thumbnails visible in the UIScrollView (+ 1 when the view is being scrolled) at any one time. I want to dequeue and reuse the thumbnail's UIView when the view is scrolled, as I am doing in the main UIScrollView (which is a paging scroll view).
Thanks -
Jk
I am also going to suggest you take a look at some sample code of Apple, that is, Photo Scroller. If you are a registered iOS developer, you should also take a look at the WWDC10 session about scroll views in iPhone applications.
http://developer.apple.com/library/ios/#samplecode/PhotoScroller/Listings/main_m.html
What you need to do is mimic the behavior of a table view (which is nothing more than a subclass of UIScrollView). What you should mimic is the reuse of the cells. It is pretty easy to implement and will dramatically reduce the memory foot print of your application since you only load the content that is currently visible in the scroll view.
I hope this helps.
Check out the scrollview suite sample code from apple. The tiled example can probably be repurposed very easily.
http://developer.apple.com/library/ios/#samplecode/ScrollViewSuite/
Check out this class..it may proove helpful..
VSScroller

Optimizing a UIScrollView with a lot of subviews

I have a UIScrollView that I am using to simulate a UITableView like interaction because rows are a bit more complex than what UITableView has to offer. I have 4 UILables a UIImageView and a UIButton for every row. The end result is a lot of subviews.
Even with only 10 rows,the scroll view that looks fine in the simulator but has a fairly low frame rate on the iPhone 4. This is a resource consumption issue for sure.
Is there a way to optimize the redraw durring scrolling like double buffering?
If not is there another way to get customizable UITableview functionality?
thanks
Does every View have 4xUILabels, a UIImageView and a UIButton?
I would create a nib file with a custom UITableViewCell (You can make those as complex as you want), then you can reuse the cells to help with your performance.
Information on how to do this is here:
http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7-SW1
I think you probably want to create a custom subclass of a UITableViewCell as the UITableView will handle all the redrawing for you. In a custom UITableViewCell you can add as many subviews as you like.
Take a look at http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html.
UITableViews are subclasses of UIScrollView (or at least conform to their behavior), but you really want to let the iPhone handle the selective drawing/cell reuse for you that the UITableView provides.
You should use UITableView if it does everything you need.
However if your tableview cells are really complicated, or you want to enable paging on the scrollview, you should take a look at the PageContol sample code that Apple provides. In a nutshell, you watch for movement in scrollViewDidScroll: and load new "pages" just before they become visible. This method works very well in practice for arbitrarily long lists of pages.