How to implement UIImageView-like delayed loading in UITableView - iphone

Background:
I have a UITableView showing an image in each cell. These images are all part of a big pdf-file. So what I am doing is actually rendering little pdf-parts in those UITableViewCells, each cell displaying just one piece. Therefore I add a UIView to the contentview of the cell and render the view on demand.
Rendering these pdf-parts is expensive and each takes about 0.2 seconds (only drawing the Pdf-part), which slows the scrolling of the table terrible down.
Idea:
Now, I know there may be a solution when I look how UIImage renders URL-based images. If you create a UIImage based on a url, these images are rendered somehow delayed. E.g have a look at the iTunes-App.
Smooth scrolling is possible, each image is displayed unrendered and after rendering is finished it appears smoothly.
Problem: How can I render an expensive rendering in a UITableViewCell like described above, by somehow showing the cell delayed? Has anybody an idea, how Apple solved it within UIImageView?
Thanks in advance
Daniel

this is (mostly) duplicate of Lazy load images in UITableViewCell. Check the links and examples there.
In short, what you need is spawning a separate thread for loading the PDF thumbnails, and that thread then signaling back to the main thread with the generated images. This is easily done by using performSelectorInBackground:withObject: and performSelectorOnMainThread:withObject:waitUntilDone: methods.

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!

UIImage not load in UITableView with lazy image loading

When I scroll to new area of the UITableView with lazy image loading, then
immdietly scroll back to the old area, no matter what I do now, the new area will not show new images.
My code is based on apple lazy table view.
Till now I thought it was something with the web server, but I had cache functionality now, and all images are loaded from iphone files, so for sure there is somthing wrong in my code.
Again, the images are not shown ONLY in that case :
UITableView is loading the data
All visible cell are showing the images correctly.
If I scroll to new area, move to old area *quickly* and move back to new area the image will not load, no matter where I scroller now(only the images in the new area that I quickly scorll back from will not load)
all on other cases, the images are shown correctly.
Without seeing the some code its hard to say. If your display is tied to a callback from the NSURLConnection you might be losing your reference to the delegate.
When you scroll the cell view get repopulated with the data in the new cell. If the NSURLConnection is running outside of the cell view you might be losing the reference to it's delegate. When the NSURLConnection is completed if it doesn't have a delegate to fire the complete method then it won't get called. When you scroll to the new cell the NSURLConnection is already complete so it doesn't call the completed again and the spinner just keeps going.
I fix it. the bug was nothing to do with all above. so nothing to share and learn from.
I thought it is important for other I'll be clear about that, so no one will be confused about what I wrote earlier.
The process of checking if image already download was wrong.

Cocoa-Touch: Is there any open-source UIImageView subclass that supports touchUpInside and initWithUrl?

It's been more than 3 years since Cocoa-Touch is out, the licensing is permissive now.
Many, many apps have UIImageViews that are actually buttons, and load from the internet.
Is there any open UIImageView subclass (or similar) that supports:
adding a target for the touchUpInside event (maybe others too)
initializing with a NSURL, loading the image from that URL (async ofc) and displaying a UIProgressIndicatorView while loading
Or is everyone pretty much rolling their own for this?
It seems to be such a common thing, yet google has no good hits.
You wouldn't want the UIImageView derived class to load its image--especially if it's in a table view because when the user scrolls the table view, the image that will display in the image view would need to change. If you are assigning a specific image to a specific image view within a table view, you run the risk that the image displaying will be out of date by time the image data has actually returned.
While I wouldn't call it trivial as Daniel said, I would say that it is simple enough that you could probably code it up fairly quickly. The idea though is that you want to have some other class, a singleton perhaps, that handles all of your image download requests and then notifies your view that contains your image view that the images are available when the requests complete.
You should also consider that you can set an image for a UIButton, so then you wouldn't need to derive your own UIImageView if all you need is -touchUpInside. You could create an action for the button and implement it pretty easily.

How to increase the scrolling performance in my table view with images in iphone?

I am new to iPhone development. I am parsing a xml and display the title, date, contents and image in the cell. Now the scrolling is not smooth, it is stuck.
How can I increase it? I have already applied lazy loading in another view, I am not able to apply in the new view. So how can I increase the scrolling performance? Can I check for any condition that if image is already loaded in the image view, so I can stop once again the loading of image view?
What I do to guarantee fast scrolling in a table is to subclass my own UITableViewCell where I implement all properties that I need.
Whenever that tablecell is initialized I draw the properties of the cell on the cell itself. So when you need an image on there, dont use an UIImageView, but just draw the image on the cell. Also all the text I need I just draw on there.
After a long stuggle finding out how to do all this, I also found a nice blog post about this.
http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/
First of all, check if you're not resizing the images - that takes a lot of computing power, and will slow down the table for sure.
Second of all, check for view hierarchy - complicated view hierarchy means bad performance. Rembemer to use as much opaque views as possible, and when you're using non-opaque views don't make the cells too complex(by complex Apple means three or more custom views). If the views are too complex - use drawRect method for custom drawing of the content.
There's a great example on how to achieve this provided by Apple called AdvancedTableViewCell (here's a link), and there's a great tutorial by Apple about table view cells (another link).

Adding UiImageView to UiScrollView one at a time... but how?

My problem is that my 300 images are being added to UiScrollView and shows up after it's done finishing with addsubview: calls.
How do I set it up so that it adds a subview, who's up on the Screen .. and continue adding ?
You could add to UIScrollView only first couple of pictures that fit the screen and then add others on request, when user scrolls you have to check bounds and add new image if it would be displayed.
I'm not sure about the code details though.
I know for a sure that UIScrollView with 3 images are shown in demo in the lecture 8 (including source code) at Stanford's CS193P course about iPhone development, the presenter (a guy from Apple) mentioned in the video (on iTunesU) that you could load up on demand other images - he just gave general pointers not the actual solution.
But if you need to display screen full of icon size images (kind of like iphone image library) maybe it would be better to try UITableView (I haven't used it for that purpose yet but give it a try).
stefanB's answer is a good one. For the code details, I'd suggest you take a look at - [UIView drawRect:]. Given the size of each of your rows, you can easily calculate when the passed rect represents a new cell being brought into view due to a scroll, and then update with the new image accordingly.
Have you thought about putting your images not in a UIScrollView, but in a UITableView and having that handle all the caching and lazy loading for you? It sounds more specialized to handle what you'd like to do.