Styling UITableViewCells with gradient backgrounds - iphone

I've been looking for ways to improve the overall attractiveness of my iPhone applications. A majority of the functionality happens in UITableView. I think I can start by adding subtle gradients to UITableViewCells, as that seems to improve the feel of the app by an order of magnitude. Selecting the appropriate fonts/sizes helps a great deal also. My question for this forum is, what's the best strategy of adding gradients to UITableViewCells? Are you using Core Graphics/Quartz? Are you using a 1x1 pixel image and stretching it? I'm interesting in something along the lines of the following screenshot of the Tumblr iPhone app: http://dl-client.getdropbox.com/u/57676/screenshots/tumblr.jpg
Does anyone have any good examples of how to make your UITableViewCell's stick out?
And for performance reasons is it better to use an image or draw with Quartz? If Quartz, I'd love to see some sample codes of how folks are drawing the gradients into the cells.
Thanks.

I work for Tumblr, and while I didn't write the iPhone app (he did), I have the source and can tell you how it's done.
In -tableView:cellForRowAtIndexPath:
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"postCellBackground.png"]];
cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"postCellBackgroundSelected.png"]];
Pretty simple, really: PNG images in a UIImageView as the cell's background views.
The two images are 1x61 vertical gradients that UIKit automatically stretches horizontally to fit the width of the cell.

As of iPhone SDK 3.0, there's a much simpler solution that relies on the new CAGradientLayer and doesn't require subclassing or images.

Marco's answer works great for plain table view cells and for grouped table view cells that are located in the middle of the table view, but if you try to set a background view for the first/last cell in a grouped table then it will break the rounded corners. To fix it you can set cell background color to a UIColor colorWithPatternImage, e.g:
cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Gradient.png"]];

I'd use Quartz 2D for this, but setting up images in Photoshop is perfectly valid too.
As for performance, if you're re-using cells by type I don't think it would be an issue - but if you do find it a bottleneck you could draw to a bitmap context once, obtain an image from that and use that everywhere - a sort of hybrid solution.

UITableViewCell supports separate views for their background and selected background, so it makes sense to make use of these properties. To do that, you're going to need to come up with an image that's the full size of your cell anyway (UIImageView doesn't stretch it's image), so you may as well make it on a desktop machine once, and save the cycles on the phone that would have to be spent to stretch an image or make it dynamically. Though if you intend to allow the user to change the gradient colors, you'll have to create it once dynamically anyway.

I think the easiest way, by far, of handling UITableView with custom cell is using Interface Builder. It makes UI design work much easier than doing it by pure core. Here is a great tutorial (with a video!) on how to do that. Highly recommended. I am not using any other method of UITableView coding since I followed that.
Having said so, adding a gradient to your cell will be extremely easy. Just use InterfaceBuilder to add an image containing your gradient to the cell view and you are done. You won't have to worry about Quartz, and performance wise you'll get similar results since CocoaTouch's components are very well optimized to do plain task such as displaying an image.

Mirko's answer is a good alternative if you don't want to use an image. However it's best to add the gradient to the backgroundView rather the UITableViewcell itself. This way you won't lose the highlight effects when selecting the cell

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!

TableViewCell in IB or Programmatically, which one is better for customization?

I'm coding a custom TableViewCell and I need to set a background image for each one (actually same background image on all cells) and I'll add some labels for different type of texts (different in font, size, color, etc...) and a left image hosted on a web server. I've read a tutorial on how to make the TableViewCell using IB and add it to cellForRowAtIndePath method. It worked but the image size and texts in labels are not showing like I was seeing them in IB, not wysiwyg at all lol
So, I need your help to choose the proper way to customize these cells, should I go for the IB way or programming tips are better?
Thx in advance for helping,
Stephane
I am guessing that the size of your images is going beyond the expected size of the imageView property for the left image. That usually pushes around all the other UI elements. You can avoid that by setting imageView.layer.masksToBounds of your cell to YES.
In general, for the problem you describe, I would recommend doing everything programmatically. You will have the opportunity for abstraction and include your program logic more economically.
To keep your cellForRowAtIndexPath method reasonably short, you can call your own formatCellAtIndexPath method and keep the formatting logic neatly separated from the content.
I find it easiest to create the TableViewCell in IB to start with and then, when I have it looking the way I like it, to switch to a cell built programatically.

How to remove separation between UITableView cells

I want to remove the built in separation between cells in UITableView.
I tried using :
[self.myTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
But that only removes the separator line.
I need the view to appear as if it's not a table view at all. (like the tableview is one big view who doesn't contain many separated cells)
Is that possible ?
Edit:
See the separation between the cells? I wan't it to disappear and the table view to be as if it's one big cell.
Edit 2:
The problem doesn't appear when I don't use an image view as the cell background, but just use a simple background color.
I tried using a different image, and as you can see the problem is much less obvious.
I would still appreciate a solution for the red image though, since I do have a lot of images that still can't be put as background currently. (Not sure why one image would cause the problem and other won't ,I guess something with the pic setting)
You can do something like
self.tableView.separatorColor = [UIColor whiteColor];
or
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
I suppose in your case it would be
self.myTableView.separatorColor = [UIColor whiteColor];
In the future you might want to search Stack Overflow as there are many similar questions.
Your separation is due to the background gradient in the red example and it is not coming from the tableView. in your second image there is no gradient so you don't see separation.
remove the gradient from the background image and it will be fine.
try to do this:
self.tableView.backgroundColor=[UIColor clearColor];
self.tableview.separatorColor=[UIColor clearColor];
self.view.backgroundColor=[UIColor yourcolor];
i don't remember if it's then color with image or backgroundwithimage or background with view.. don't remember, however logic way is this.
hope it's usefull
If you just want to get rid of the separator between UITableView cells. Two things you need.
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
Make sure your imageView ( in your tableview cell) has same height as tableview cell. This means your image view is fully covering the table view cell. Goto inspector view and verify the height , if you see the height is 43 just set it to 44 ( 44 is default tableview cell height for retina display).
Go StroyBaord -> Select Table -> Attribute inspector -> Separator None
No Separator Will Appear
You seem to have answered your own question. If you want a solid colored background, make an image with just a solid color. I suspect it's not best practice, but it will get the job done.
I've done this many times in my own applications. I understand that you wish to make your tableview look as if its not a tableview but as if its on a blank piece of paper.
Just a note for you, the reason you can see the separation with some images and not so much with others is due to its image texture. When you place copies of the image side by side, they dont have a continuous pattern thus forming obvious visual separations.
Solution:
Assuming you have the tableview setup in a xib file, display the background of the table itself to to clear -> then set the background of the view itself to whatever image you like. You will then have a tableview with the background view being transparent with only the main view behind it showing its image or color. Displaying your image on this canvas will mean a clear one image background.

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).

How to add gradient at bottom of UITableViewCell as Tweetie app does

Does anybody know how:
add small gradient to bottom of each UITableViewCell to visually highlight separation between cells
and at the same time make each second cell in table a bit darker then the each first row.
A great example of that stuff is Tweetie app. When you tap on your twitter account name you'll see table view with twitts. Even rows are bit lighter and each row has tiny dark gradient at the bottom, that visually separates rows. Looks pretty good.
Could anybody give me a clues how to do that?
I'm not sure if this is what you want, as I don't use Tweetie (though been meaning to check it out), but Cocoa With Love has a good discussion of the new CAGradientLayer available in 3.0. And an older one that relies on custom background images behind the cells.
I haven't looked at Tweetie. You can add a UIImageView to your cells above other items, with a gradient image and an alpha of 50% or less. With the right gradient and alpha, that will make the top and bottom of each cell look different - sort of like each cell is curved.
If you want every other cell to look different, then in cellForRowAtIndexPath, add a differently colored gradient to the UIImageView above, for odd cells vs even cells. Or change the background UIColor for the cell.