30

I have a standard grouped table view. I would like to add a shadow around it (i.e. around the edge of each tableview section) - if you are not sure what I mean, then see the official twitter app (below) for an example. It's pretty subtle, but it's definitely a shadow as opposed to a border.

Twitter app

How can I achieve this effect?

Save for using images with built in shadows as each cell's background - which won't allow animated cell resizing like I need - I haven't figured out a way.

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
  • You could use this [custom background/border code][1] and add the shadow to it. [1]: http://stackoverflow.com/questions/400965/how-to-customize-the-background-border-colors-of-a-grouped-table-view – Eric Dec 25 '11 at 17:47
  • @Eric thanks, but I've tried this. I couldn't figure out a way to draw a shadow around only two or three sides of a cell. Drawing it around all sides means you see overlaps where each cell joins. – Jordan Smith Dec 25 '11 at 23:47
  • I have a feeling this is a plain style tableview that is made to look like it's grouped. Imagine a plain table view with clear background and no separator style, then each custom cell has a custom UIView added to it - this view "knows" whether it is first, middle or last row and draws itself with rounded corners + shadows based on that. – Rog Jan 01 '12 at 09:34
  • Here you go - this is sort of what I am talking about, except that the drawing of the cell and shadow in twitter's case is done programatically via `drawRect:` http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html – Rog Jan 01 '12 at 11:05

5 Answers5

26

I have two solutions that might help. Because of the rounded corners, I think you will have to apply the shadow effect to the cells, and position them so that the shadows do not overlap on the y axis. So...

Solution 1 (the complicated solution): This is the solution that I think could work if you are animating the cornerRadius, or are unsure of the exact shape of the section of cells. Have you tried using the shadowPath like Ecarrion suggests, but applying it to your custom UITableView cell? Imagine a path for the central cells (not the top and bottom) that is slightly wider than the cell on the x axis and shorter on the y axis.

Then you have to make sure the shadow is cast at the top and bottom of the section without making each individual shadow overlap, right? So for the top and bottom cells you make the shadowPath larger on the y axis, let's say 4 points larger. Then you adjust the shadowOffset property for those cells, also by 4 points, on the y axis. If the shadowOffset of a central cell is (0,0), then the top cell is (0,-4) and the bottom cell is (0,4).

If it was me, I would put the various size and offset adjustments in a plist so I could tinker with the small details without editing the code. Just load the plist into a dictionary or custom class and then set your properties using those values. Makes the little adjustments a lot less fidgety.

EDIT: Going beyond using shadowPath, you can make a composite cell for the tableview with a transparent background layer a slightly smaller shadow layer, and yet a smaller layer for adding text and images. The shadow layer is based on the text/image layer, maybe slightly wider, taller, shorter etc. Apply a blur to that layer, and then mask it with a rectangular mask so that where one shadow layer ends the next begins. With the top and bottom layers, the mask must extend up or down to the highest/lowest point on the y axis that the shadow effect appears (you still have to mask the side that abuts the next cell). It depends on how far you're willing to go to achieve an exact effect... maybe this gives you some more ammunition.

Solution 2 (the somewhat easier solution): If you know exactly what the cornerRadius will be on the top and bottom cells, and the only thing you don't necessarily know is the length and/or width of the cells, you can use the old "stretched one-pixel graphic" trick.

First you Photoshop/Gimp an image for the corner shadow. If the width is always the same, this image can be the shadow for the whole top of the cell. The same image can be used for the other corners or the bottom of the cell by rotating it.

Now the trick. Take a 1 x 5 section of the blur or gradient effect you used for the corner shadow. If your shadow effect is 3 pixels, make that 1x3, etc. Export to PNG or preferred format and check that if you place its edge against an edge of the corner image it lines up seamlessly (the blur or gradient has the same color values without a visible seam).

All cells get a layer next to the visible part of the cell (to the left and right) with your 1x5 image as the layer contents. Could also be a stretched UIImageView, your choice. These line up the make the left and right shadows.

Place your corner shadows on the first and last cells in the section -- they can be different custom cells, or you can design your cell so that it gets its index passed to it at creation and arranges itself appropriately. Align your side shadows so that they start where the corner shadow ends, and stretch down to the bottom of the cell. If you are also changing the width of cells, you'll do the same thing between the two corner images, stretching your gradient across the x axis.

Ok, a picture says a thousand words, so since I seem to be pushing that many words here's an image to illustrate:

stretching a 1px image for a scalable border or shadow effect

Rab
  • 2,806
  • 2
  • 16
  • 20
  • 1
    Wow thanks! I never realized that you could change the frame of a shadow path, so that definitely helps. I'm not planning on implementing any corner resizing for this - so in fact the second method will work, and probably will be more efficient. – Jordan Smith Jan 01 '12 at 23:27
  • Glad it helps. I would always use the simpler image stretching method when possible. It is generally easy on the processor and light on memory usage, and not so hard to implement, so it's win-win-win. – Rab Jan 02 '12 at 05:05
14

I needed this effect in one of my own applications the other day. It's actually very easy to achieve by just using the CALayer shadow properties in combination with a CALayer mask. I've put together a simple example project at Github:

https://github.com/schluete/GroupedTableViewWithShadows

The effect is implemented in the UITableViewCell category UITableViewCell+CellShadows.m.

The first step is to create a shadow rectangle. Enlarge the rectangle depending on the cell's position in the section (top, middle, bottom) to prevent the rounded corners from being visible in the wrong corners (lines 18-23). Then add the shadow to the background view of the cell (lines 35-41).

Now there's a nice shadow effect, but the shadow of the first cell "bleeds" into the second cell. To prevent the bleeding just add a layer mask to cut off all the unnecessary parts of the shadow (lines 25-32 and 43-46). That's it, we've got our shadow!

Axel Schlueter
  • 164
  • 1
  • 2
  • Unfortunately this solution breaks down on cell selection or rotation. – tyler Dec 06 '12 at 18:46
  • Great work! This does exactly what I needed. It doesn't break if you apply the same effect to the selectedBackgroundView. – Lizza May 10 '13 at 22:07
  • Nice, just what I was looking for. I was suffering from the 'bleeding' as well in my own solution. I didn't know about the layer mask. – CaseyB Jul 11 '13 at 23:26
  • An updated version in Swift 4 https://gist.github.com/pdura/740603112c180a2710cbffe533687bbc – Patrick Dura Mar 11 '19 at 14:01
2

Actually you could try to use a normal mode tableView (not grouped) and create 2 (or more) kind of cells with a specific identifier for each. One for the top one with top/left/right shadow, one for the last with left/right/bottom shadow, and one for the other cells with left/right shadow. In your tableView:cellForRowAtIndexPath: just check at which row you are (ex. indexPath.row == 0) and return the proper cell.

Vincent Zgueb
  • 1,491
  • 11
  • 11
  • If I add a shadow to each cell, it overlaps the other cells where they join. Is there a way to add a shadow around just two or three sides of a cell? I've tried that but couldn't figure out a way to do it. – Jordan Smith Dec 25 '11 at 23:44
  • I just modified the answer. See above. – Vincent Zgueb Dec 27 '11 at 08:47
  • that's pretty much what I'm doing at the moment (and I'm just using different images for top/mid/bottom cells). But like I said in the question, it's not very flexible and won't allow things like animated cell resizing. So, like the question says, I need to figure out a different way of doing it. – Jordan Smith Dec 27 '11 at 12:27
  • What kind of animations do you need ? Resizing cell should be Ok if you set properly the autoresizingMask. – Vincent Zgueb Dec 27 '11 at 13:33
  • nope, not when you're using a texture as a cell background it won't... so a real shadow is the only way. – Jordan Smith Jan 01 '12 at 01:18
  • I think the path you're using for the shadows is just too tall on the y axis. Maybe squash it a bit and let the two gaussian blurs overlap just enough so that they make an even transition. – Rab Jan 01 '12 at 06:52
0

At the risk of bringing up an old question, I feel that none of the answers provided have brought forth any decent coding practices, nor are they "clean and easy".

I discovered a much cleaner approach to this. See what you think. Firstly, you will need to subclass UITableViewCell. Then, either within the header or elsewhere, make three separate definitions (for example):

#define TOP_CELL 0
#define MIDDLE_CELL 1
#define BOTTOM_CELL 2

Now, override the layoutSubviews: method and do something like the following:

-(void) layoutSubviews {
    [super layoutSubviews];

    if([self tag] == BOTTOM_CELL) {

         self.layer.masksToBounds = NO;
         self.layer.shadowOffset = CGSizeMake(-10, 15);
         self.layer.shadowRadius = 5;
         self.layer.shadowOpacity = 0.5;
    }
    //else if middle_cell (Different shadowing)
    //else if top_cell (Different shadowing)
}

NOTE: I am not actually sure if the layoutSubviews method is the adequate method to do this in, I just know it gets called after dequeueReusableCellWithIdentifier: which is important for the next step.

Now, within your UITableViewDataSource, in your tableView:cellForRowAtIndexPath:

CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:ident];

if(indexPath.row == 0)
    [cell setTag:TOP_CELL];
else if(indexPath.row == (NUMBER_OF_ROWS - 1))
    [cell setTag:BOTTOM_CELL];
else
    [cell setTag: MIDDLE_CELL];

voila! shadowed sections

taylorcressy
  • 966
  • 8
  • 23
-2

You could always set a ShadowPath like this:

myView.layer.shadowOpacity = 0.8;
myView.layer.shadowRadius = 5.0;
myView.layer.shadowOffset = CGSizeZero;
myView.layer.shadowPath = [UIBezierPath bezierPathWithRect:myView.layer.bounds].CGPath;

Note that this requires QuartzCore or CoreGraphics, cannot remember well.

In this article there are a lot of shadow paths: http://nachbaur.com/blog/fun-shadow-effects-using-custom-calayer-shadowpaths

Ecarrion
  • 4,940
  • 1
  • 33
  • 44
  • Each table view section is not a view in itself - this code would work, except for the fact that there's nothing appropriate to use as "myView". Either, there is access to the whole tableview, sections and all, or each cell separately. It is accessing the view that holds each section that cannot be done (at least easily). I've tried similar techniques but to no avail! – Jordan Smith Dec 24 '11 at 22:22
  • You could add a headerView and a footerView to a section and add the shadow to those views. – tableView:viewForHeaderInSection: – tableView:viewForFooterInSection: – Ecarrion Dec 24 '11 at 22:29
  • That doesn't help, there's no shadow around the corners or sides that way. I've tried this way too... The answer is not as simple as you assume - I've tried every obvious solution I can think of. However twitter does it must be more complex or use some sort of hack to work as well as it does. – Jordan Smith Dec 24 '11 at 22:41
  • Just add the shadow to the first cell in the section, and alter the frame of the shadow to multiply its height by the number of cells. – Rik Smith-Unna Sep 06 '12 at 13:09