13

I recently came across this brilliant article about improving scroll performance with UITableViewCells: http://engineering.twitter.com/2012/02/simple-strategies-for-smooth-animation.html -- While many great tips can be found in this article, there is one in particular that has me intrigued:

Tweets in Twitter for iPhone 4.0 have a drop shadow on top of a subtle textured background. This presented a challenge, as blending is expensive. We solved this by reducing the area Core Animation has to consider non-opaque, by splitting the shadow areas from content area of the cell.

Using the iOS Simulator, clicking Debug - Color Blended Layers would reveal something like this:

TwitterTableViewCell

The areas marked in red are blended, and the green area is opaque. Great. What the article fails to mention is: How do I implement this? It is my understanding that a UIView is either opaque or it's not. It seems to me that the only way to accomplish this would be with subviews, but the article explicitly states that as being a naive implementation:

Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.

So how do I section off what is opaque, and what is not in my single drawRect: method?

nrj
  • 1,701
  • 2
  • 22
  • 37

3 Answers3

3

In the example you show, I don't believe they're showing a background through the view. I think they're simulating a background in core graphics. In other words, in each cell they draw a light gray color for the background. They then draw the shadow (using transparency), and finally they draw the rest of the opaque content on the top. I could be wrong, but I don't believe you can make portions of the view transparent. If so, I'd be very, very interested in it because I use core graphics all the time, but I avoid rounded corners because blending the entire view for it just doesn't seem to be worth it.

Update

After doing some more research and looking through Apple's docs, I don't believe it's possible for only part of a view to be opaque. Also, after reading through Twitter's blog post, I don't think they are saying that they did so. Notice that when they say:

Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.

They were specifically talking about UILabel and UIImageView. In other words, instead of using those views they're drawing the image directly using Core Graphics. As for the UILabels, I personally use Core Text since it has more font support but they may also be using something simpler like NSString's drawAtPoint:withFont: method. But the main point they're trying to get across is that the content of the cell is all one CG drawing.

Then they move to a new section: Avoid Blending. Here they make a point of saying that they avoid blending by:

splitting the shadow areas from content area of the cell.

The only way to do this is to use different views. There are two approaches they could be using, but first note that the cell dividers are themselves overlays (provided by the tableView). The first way is to use multiple views inside the cell. The second way is to underlay/overlay the shadows/blended-views behind/over the cells by inserting the appropriate views into the UIScrollView. Given their previous statement about having only one view/drawRect for each cell, this is probably what they're doing. Each method will have its challenges, but personally I think it would be easier to split the cell into 3 views (shadow, content, shadow). It would make it a lot easier to handle first/last cell situations.

Aaron Hayman
  • 8,492
  • 2
  • 36
  • 63
  • Sure I can draw gradients and transparent colors, but in the Tweet cell the shadows are on the edges and let the background show through. This implies that the view that I'm drawing them into is *non* opaque, otherwise you would see a black background behind the shadows instead of the view behind them. – nrj May 14 '12 at 17:01
  • I don't think they're showing through a background. I think they're simulating a background in their cell view. I'll update my answer to explain. – Aaron Hayman May 14 '12 at 17:33
  • The article states: "Tweets have a drop shadow on top of a subtle textured background. This presented a challenge, as blending is expensive." -- If they are only *simulating* a background, then they wouldn't need to blend anything at all and the entire cell view would be green when you toggle "Color Blended Layers". – nrj May 14 '12 at 17:35
  • You may need to contact them about it (it they're willing). The 'subtle textured background' can easily be created in the cell view. However, that background will scroll with the view. If you find the answer, please post it. I'm quite interested in this. – Aaron Hayman May 14 '12 at 17:39
  • @nick I've done some research and updated my answer. Unfortunately, I don't think it's possible to only make part of a view opaque. I wish it were (it would make my life a lot easier). I also don't think they claim they do, it's just seems that way at first glance (see my updated answer). – Aaron Hayman May 16 '12 at 12:56
0

I'd have to guess something along these lines
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadows/dq_shadows.html

  CGContextRef context = UIGraphicsGetCurrentContext();

UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10.0f];

CGContextSaveGState(context);
CGRect leftRect = CGRectZero;
CGContextClipToRect(context, leftRect );
CGContextSetBlendMode(context, kCGBlendModeNormal);
// draw shadow

//    Call the function CGContextSetShadow, passing the appropriate values.
//    Perform all the drawing to which you want to apply shadows.
CGContextSetShadowWithColor(context, CGSizeMake(1.0f, 1.0f), 10.0f, [UIColor blackColor].CGColor);
CGContextAddPath(context, path.CGPath);
CGContextDrawPath(context, kCGPathStroke);


CGContextRestoreGState(context);

CGContextSaveGState(context);
CGRect middleSection = CGRectZero;
CGContextClipToRect(context, middleSection);
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
CGContextFillRect(context, self.bounds);
// draw opaque
CGContextSetBlendMode(context, kCGBlendModeCopy); 

CGContextRestoreGState(context);
Nico
  • 3,826
  • 1
  • 21
  • 31
  • In the Tweet cell above, the shadow is along the left and right side of the view, on top of another background view. I can use this code to draw a shadow, but unless I do `[cell setOpaque:NO]` there will be a black background behind the shadow. What I want is for the background view to show through. – nrj May 14 '12 at 16:55
  • Render the background image, clip/render shadow left, clip/render shadow right, render foreground. – Nico May 14 '12 at 18:24
  • also you may want kCGBlendModeScreen instead of normal blend. – Nico May 14 '12 at 18:26
  • I will look into this and report back. Thanks. – nrj May 14 '12 at 22:31
0

My opinion is: Don't let Core Animation draw shadows using the various layer properties. Just draw a prerendered image to both sides, which is in fact a shadow. To factor variable height of a cell in a stretch draw may do the trick.

EDIT: If the background is plain a prerendered shadow can be applied to both sides without know it is affecting visual appeal.

In case that is not applicable the tableview has to be shrunk to be of the size without the shadow. Then the shadow can be blended without doing it for every cell but just "on top". It really doesn't scroll. This will only work if the shadow is without any "texture", else one will notice it's just applied on top.

Nick Weaver
  • 47,228
  • 12
  • 98
  • 108
  • Doesn't matter how you draw the shadow. In order for the background to show through it, the entire view would have to be set to non-opaque. – nrj May 14 '12 at 22:30
  • It does matter, I think I may have not described it properly enough. If the background is static you just don't have to blend, therefore a prerendered shadow, already blended with a static background is just drawing without blending. Please see my edit. – Nick Weaver May 15 '12 at 06:45
  • Yes I realize that this particular example could be done without blending. See Aaron's comment above about just simulating the background. My question is not "how do I create this cell", there are several ways to do it. I want to know how they were able to blend only a section of the view without marking the entire view as non-opaque, and without using subviews. Learning how to do this would be very valuable in other situations where drawing a static background is not an option. – nrj May 15 '12 at 15:05