How could I add a shadow to a grouped UITableView (as seen in the official twitter app)?

11,655

Solution 1

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!

Solution 2

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

Solution 3

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.

Share:
11,655
Jordan Smith
Author by

Jordan Smith

Blog: jordansmith.io Founder: classtimetable.app

Updated on June 18, 2022

Comments

  • Jordan Smith
    Jordan Smith almost 2 years

    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.

    • Eric
      Eric over 12 years
      You could use this [custom background/border code][1] and add the shadow to it. [1]: stackoverflow.com/questions/400965/…
    • Jordan Smith
      Jordan Smith over 12 years
      @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.
    • Rog
      Rog over 12 years
      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
      Rog over 12 years
      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: cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.ht‌​ml
  • Jordan Smith
    Jordan Smith over 12 years
    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!
  • Ecarrion
    Ecarrion over 12 years
    You could add a headerView and a footerView to a section and add the shadow to those views. – tableView:viewForHeaderInSection: – tableView:viewForFooterInSection:
  • Jordan Smith
    Jordan Smith over 12 years
    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
    Jordan Smith over 12 years
    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.
  • Vincent Zgueb
    Vincent Zgueb over 12 years
    I just modified the answer. See above.
  • Jordan Smith
    Jordan Smith over 12 years
    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.
  • Vincent Zgueb
    Vincent Zgueb over 12 years
    What kind of animations do you need ? Resizing cell should be Ok if you set properly the autoresizingMask.
  • Jordan Smith
    Jordan Smith over 12 years
    nope, not when you're using a texture as a cell background it won't... so a real shadow is the only way.
  • Rab
    Rab over 12 years
    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.
  • Jordan Smith
    Jordan Smith over 12 years
    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.
  • Rab
    Rab over 12 years
    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.
  • Rik Smith-Unna
    Rik Smith-Unna over 11 years
    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.
  • tyler
    tyler over 11 years
    Unfortunately this solution breaks down on cell selection or rotation.
  • Lizza
    Lizza about 11 years
    Great work! This does exactly what I needed. It doesn't break if you apply the same effect to the selectedBackgroundView.
  • CaseyB
    CaseyB almost 11 years
    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.
  • Patrick Dura
    Patrick Dura about 5 years