Best way to include UIImage in custom UITableViewCell? - iphone

I want to know what is the best and most optimized way to draw UIImage in a custom UITableViewCell that has many elements in it (several images, 2 labels, gradient background). I read that drawRect is the recommended way to go with this since there are several subviews involved and its better to have them all composed as one view content using drawRect. But at the same time I read somewhere else that UIImageView is the preferred way to optimally handle images (caching, fast rendering, etc). I'd appreciate some enlightenment.
Thanks
AF

I would recommend UIImageView for its easiness of use. You can drag & drop it in the Interface Builder or just add it programmatically.

I went to an iPhone Tech Talk once, and a performance engineer said if you're using a UIImage in a UITableView you should add it with the class method [UIImage imageNamed:] to get the most efficient use of the class. Of course you would need to embed that in a UIImageView. I usually find any time there are class methods available to make something for you, Apple is very efficient at the way they do it.

I would suggest you to use
cell.imgView.image = [UIImage imageNamed:#"123.jpg"];

Related

UIWebView: Tracking screen updates (dirty regions)

I'm trying to detect animations and other screen updates as they happen inside a UIWebView. I would like to get the rectangles of areas in the UIWebView that have been modified since the last refresh.
I think really what I'm looking for is for a way to "trap" the calls that UIWebView makes to setNeedsDisplayInRect. Is there a way to do that? Can I somehow subclass UIWebView's underlying CALayer object in a way that would allow me to catch those calls as they come in from UIWebView?
There's no good way of doing that. You can try grabbing a snapshot of the UIWebView's CALayer and comparing it to the previous snapshot, but I've had a lot of trouble getting reliable snapshots of UIWebViews.
Use an Objective-C category (#implementation CALayer (MyCALayer)) - like you're already doing based on your update - to trap the calls going from UIWebView to CALayer.
Then, use Method Swizzling to relay your category overrides to the original CALayer object.

Why not UIImage itself but UIImage view

I'm learning to develop apps for Iphone. I follow a book by Apress which I find very useful. But as nothing is perfect some issues are not well described and just skipped.In one of the applications I have to assign five images to each of the five components of a pickerview. But my question is why do/can not we use an instance of UImage itself but UIImageView to display on the picker.
As in above question you are asking why we can not use UIImage instead of UIImageView as component for UIPickerView.
UIImage is subclass of NSObject class, If we talk in terms of M-V-C its a M(model). and model in itself is nothing until its used.
UIImageView on the other hand is subclass of UIView which stands for V(view) in M-V-C so it uses your model for its contents.
So, they are two different things, not alternate. Also, if you go through UIPickerView's class documentation then you will find that it has method
- (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component
that configures view for specified row component. so it meanse we need view(UIImageView) and not the model class(UIImage). here for component you can return subclass of UIView.
Please refer apple's documentation.
Thanks,
You need a frame to place your photo, similarly You need UIImageView as place holder to place UIImage.
UIImage is just an Image loaded from file. View is able to handle touch events and only Views are displayed.

Scrolling performance and UIImage drawing

I'm building a UITableView similar to iPod.app's album browsing view:
I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:
Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = #"bigCell";
SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];
[cell setNeedsDisplay];
return cell;
My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:
[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];
Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.
All my views are opaque. What am I missing?
EDIT: here's what Shark says:
I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.
Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?
EDIT: retaining the image
I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:
- (UIImage*)artworkImage {
if(artworkImage == nil)
artworkImage = [[UIImage imageWithData:self.image] retain];
return artworkImage;
}
So now I can directly set the retained image to my TableViewCell as follow:
cell.artworkImage = artist.artworkImage;
I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.
Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:
As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:
[self performSelectorOnMainThread:#selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];
Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.
When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.
Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "...#2x.png" images for retina. Good luck!
[UIImage imageWithData:] doesn't cache.
This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.
I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.
Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.
SetNeedsDisplay is not a direct call to drawRect:
It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.
If the delay's happening in your -drawRect, then you might want to take a look at this article: Tweetie's developer explains pretty thoroughly the method he used to get that smooth scrolling you're after. This has become a bit easier since then, though: CALayer has a shouldRasterize property that basically flattens its sublayers into a bitmap, which can then—as long as nothing changes inside the layer—give you much better performance when animating the layer around, as UITableView does when you scroll it. In this case, you'd probably apply that property to your individual UITableViewCells' layers.
My guess is that the delay is from storing images in Core Data. Core Data is usually not a good way to store large blobs of data.
A better solution would be to store the images as individual files on disk, using an album id to identify each image. Then you would setup an in memory cache to store the images in RAM for fast loading into your UIImageViews. The loading of the images from disk to RAM would ideally need to be done on a background thread (e.g. try performSelectorOnBackgroundThread) so that the I/O doesn't bog down the main thread (which will impact on your scrolling performance).
Addendum 1
If you're only calling -drawRect: once per cell load (as you should be), then the problem might be the scaling of the images. Scaling an image by drawing it in code using drawInRect will use CPU time, so an alternative approach is to scale the images as you receive them from the iPod library (if you need them in multiple sizes, save a version in each size you require). You may need to do this on a background thread when you import the data to avoid blocking the main (UI) thread.
One alternative thing to consider is that UIImageView may do it's scaling using Core Animation which would mean it is hardware accelerated (I'm not sure if this is actually the case, I'm just guessing). Switching to a UIImageView for the image would therefore get rid of the CPU burden of image scaling. You would have a slight increase in compositing overhead, but it might be the easiest way to get closer to "optimum" scrolling performance.
At this point your best bet is to use Instruments (previously Shark) to try and find bottlenecks in your code.

Cocoa-Touch: How to set the interpolation quality to be used by a UIImageView?

I have a UIImageView showing a image that is larger than it's frame.
It's set to rescale the image to fit it's frame. But, the image is scaled with a low quality filter.
I've read here that this is caused by it using a low interpolation quality.
How can I get it's context to CGContextSetInterpolationQuality to kCGInterpolationHigh?
From "UIImageView scaling/interpolation", this is the most streamlined way to do it if you can:
[[yourimageview layer] setMagnificationFilter:kCAFilterTrilinear]
Be sure to #import <QuartzCore/CALayer.h>
A warning on kCAFilterTrilinear: "Some renderers may ignore this, or impose additional restrictions, such as source images requiring power-of-two dimensions."
UIImageView does not offer this functionality, though UIImage has an undocumented _imageScaledToSize:interpolationQuality: method if I remember correctly.
Since UIImageView draws directly to the display, subclassing and overriding drawRect: is no option (thanks to Prody for pointing this out). The only option I see is to create a custom UIView subclass with a custom drawrect: implementation.
CGContextSetInterpolationQuality is a function. You need to call it with whatever parameters are appropriate for your situation.
http://developer.apple.com/mac/library/qa/qa2001/qa1186.html

Possible to copy CALayer from UIView?

Here's my setup: I have a CALAyer to which I want to add sublayers. I create these sublayers by setting upa UILabel and then adding the UILables layer to my main layer. Of course this leaves the heavy UILabel object hovering around in the background. Is it possible to get the layer with all its content from a UIView and get rid of the UIView itself?
I already tried this:
UILabel* label;
[...]
[mainLayer addSublayer:[label.layer copy]];
[label release];
But whenever I release the UIView, the content of the layer is also removed. Is this even possible or does the UIView's layer always need the UIView itself to show its content? I thought of the layer as a kind of canvas, to which the UIView paints. I guess I could be wrong with this assumption :)
I can't understand why you wouldn't be able to copy a layer. True a layer is an "integral part" of a UIView. But in the end it is just another object with several properties.
Actually there is a method for CALayer called:
- (id)initWithLayer:(id)layer
But it isn't intended to make a copy of a layer. (You can read Apple's docs for the reasoning why)
CALayer does not conform to NSCopying, so you have two options:
Subclass it and implement
"copyWithZone:" (and conform to
NSCopying)
Write a method/function that will
return a "copy" of a CALayer
Regardless of which way you choose, the question you have to ask is: Which properties of the CALlayer do you want to copy?
Let's say you want to copy just the contents and frame:
CALayer copyLayer = [CALayer layer];
copyLayer.contents = theLayerToBeCopied.contents;
copyLayer.frame = theLayerToBeCopied.frame;
return copyLayer;
You could go through and copy every property of the layer, or just copy the ones you need. Might be a good method to put in a CALayer category.
It is not possible: a layer is a property of a UIView. Therefore, when you release the UIView, its layer is gone. Your idea of thinking of the layer as a kind of canvas is not wrong. But, the layer is an integral part of its UIView.
UIViews are not that heavy in iOS. For the most part, you can think of them as a supporting wrapper around a CALayer. Unlike on Mac where a NSView doesn't have to be backed by a CALayer, that's not true in iOS. All UIView instances are CALayer-backed and that's where most of the heavy lifting is. Apple's docs even say things like 'Don't bother animating a UIView's layer. Animate the UIView directly as it's essentially just sending it all down to the layer anyway.' (Paraphrased, of course.)
Point being, unless you specifically need UILayer instances for something that's not directly supported by a UIView, just stick with working with UIViews entirely and you should be good to go.
UILabel really isn't a very complex control. I suggest you give up this line of attack and learn how to draw what you want using Quartz into a fresh CGImage. You can then assign this to the contents property of the CALayer without having to worry about all this other stuff.
You can also circumvent UILabel entirely and create your text with a CATextLayer.
use CAReplicatorLayer, you will be able to completely replicate that layer