UITableViewCell: rounded corners and shadow

SwiftUitableview

Swift Problem Overview


I'm changing the width of a UITableViewCell so that the cell is smaller but the user can still scroll along the edges of the tableview.

override func layoutSubviews() {        
    // Set the width of the cell
    self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width - 40, self.bounds.size.height)
    super.layoutSubviews()
}

Then I round the corners:

cell.layer.cornerRadius = 8
cell.layer.masksToBounds = true

All good so far. Problem happens with the shadow. The bounds are masked, so the shadow obviously won't show up. I've looked up other answers but can't seem to figure out how to round the corners along the bounds and show the shadow.

cell.layer.shadowOffset = CGSizeMake(0, 0)
cell.layer.shadowColor = UIColor.blackColor().CGColor
cell.layer.shadowOpacity = 0.23
cell.layer.shadowRadius = 4

So my question – how do I reduce the width, round the corners, and add a shadow to a UITableViewCell at the same time?

Update: Trying R Moyer's answer

Trying R Moyer's answer

Swift Solutions


Solution 1 - Swift

This question comes at a good time! I literally JUST solved this same issue myself.

  1. Create a UIView (let's refer to it as mainBackground) inside your cell's Content View. This will contain all of your cell's content. Position it and apply necessary constraints in the Storyboard.

  2. Create another UIView. This one will be the one with the shadow (let's refer to it as shadowLayer). Position it exactly as you did mainBackground, but behind it, and apply the same constraints.

  3. Now you should be able to set the rounded corners and the shadows as follows:

    cell.mainBackground.layer.cornerRadius = 8  
    cell.mainBackground.layer.masksToBounds = true
    
    cell.shadowLayer.layer.masksToBounds = false
    cell.shadowLayer.layer.shadowOffset = CGSizeMake(0, 0)
    cell.shadowLayer.layer.shadowColor = UIColor.blackColor().CGColor
    cell.shadowLayer.layer.shadowOpacity = 0.23
    cell.shadowLayer.layer.shadowRadius = 4
    

However, the problem here is: calculating the shadow for every single cell is a slow task. You'll notice some serious lag when you scroll through your table. The best way to fix this is to define a UIBezierPath for the shadow, then rasterize it. So you may want to do this:

cell.shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: cell.shadowLayer.bounds, byRoundingCorners: .AllCorners, cornerRadii: CGSize(width: 8, height: 8)).CGPath
cell.shadowLayer.layer.shouldRasterize = true
cell.shadowLayer.layer.rasterizationScale = UIScreen.mainScreen().scale

But this creates a new problem! The shape of the UIBezierPath depends on shadowLayer's bounds, but the bounds are not properly set by the time cellForRowAtIndexPath is called. So, you need to adjust the shadowPath based on shadowLayer's bounds. The best way to do this is to subclass UIView, and add a property observer to the bounds property. Then set all the properties for the shadow in didSet. Remember to change the class of your shadowLayer in the storyboard to match your new subclass.

class ShadowView: UIView {
    override var bounds: CGRect {
        didSet {
            setupShadow()
        }
    }

    private func setupShadow() {
        self.layer.cornerRadius = 8
        self.layer.shadowOffset = CGSize(width: 0, height: 3)
        self.layer.shadowRadius = 3
        self.layer.shadowOpacity = 0.3
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 8, height: 8)).cgPath
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = UIScreen.main.scale
    }
}

Solution 2 - Swift

The accepted answer works but adding an extra subview to get this effect make little to no sense. Here is the solution that works.

1st step: Add shadow and corner radius

// do this in one of the init methods
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)

    // add shadow on cell
    backgroundColor = .clear // very important
    layer.masksToBounds = false
    layer.shadowOpacity = 0.23
    layer.shadowRadius = 4
    layer.shadowOffset = CGSize(width: 0, height: 0)
    layer.shadowColor = UIColor.blackColor().CGColor

    // add corner radius on `contentView`
    contentView.backgroundColor = .white
    contentView.layer.cornerRadius = 8
}

2nd step: Mask to bounds in willDisplay

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // this will turn on `masksToBounds` just before showing the cell
    cell.contentView.layer.masksToBounds = true
}

Bonus: Smooth scrolling

// if you do not set `shadowPath` you'll notice laggy scrolling
// add this in `willDisplay` method
let radius = cell.contentView.layer.cornerRadius
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: radius).cgPath

Solution 3 - Swift

cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true

Solution 4 - Swift

To create shadow and corner for cell you need only one backView. See my example below.

enter image description here

You have to add backView and set leading, trailing, top, bottom constraints equal to Content view. Put you content to backView with appropriate constraints, but be sure your content not over cover backView.

After that in your cell initialisation code add these lines:

override func awakeFromNib() {
    super.awakeFromNib()
    
    backgroundColor = Colors.colorClear
    
    self.backView.layer.borderWidth = 1
    self.backView.layer.cornerRadius = 3
    self.backView.layer.borderColor = Colors.colorClear.cgColor
    self.backView.layer.masksToBounds = true
    
    self.layer.shadowOpacity = 0.18
    self.layer.shadowOffset = CGSize(width: 0, height: 2)
    self.layer.shadowRadius = 2
    self.layer.shadowColor = Colors.colorBlack.cgColor
    self.layer.masksToBounds = false
}

Don't forget to create IBOutlet for Back View.

And here the result:

enter image description here

Solution 5 - Swift

I have achieved the same thing using following code.But you have place it in layoutSubviews() method of your TableViewCell subclass.

self.contentView.backgroundColor = [UIColor whiteColor];
self.contentView.layer.cornerRadius = 5;
self.contentView.layer.shadowOffset = CGSizeMake(1, 0);
self.contentView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.contentView.layer.shadowRadius = 5;
self.contentView.layer.shadowOpacity = .25;
CGRect shadowFrame = self.contentView.layer.bounds;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
self.contentView.layer.shadowPath = shadowPath;

Solution 6 - Swift

One alternative approach you can try, take a UIView in UITableViewCell. Set background color of UITableViewCell to clear color. Now, you can make round corners and add shadow on UIVIew. This will appear as if cell width is reduced and user can scroll along the edges of the tableView.

Solution 7 - Swift

Regarding answer R Moyer, the solution is excellent, but the bounds are not always installed after the cellForRowAt method, so as a possible refinement of his solution, it is to transfer the call of the setupShadow() method to the LayoutSubview() for example:

class ShadowView: UIView {

    var setupShadowDone: Bool = false
    
    public func setupShadow() {
        if setupShadowDone { return }
        print("Setup shadow!")
        self.layer.cornerRadius = 8
        self.layer.shadowOffset = CGSize(width: 0, height: 3)
        self.layer.shadowRadius = 3
        self.layer.shadowOpacity = 0.3
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, 
byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 8, height: 
8)).cgPath
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = UIScreen.main.scale
    
        setupShadowDone = true
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        print("Layout subviews!")
        setupShadow()
    }
}

screenshot

Solution 8 - Swift

Never use UIBezierPath , bezierPathWithRect:shadowFrame etc as its really heavy and draws a layer on top of the views and would require to reload the table view again to make sure the cells are rendering in the right way and sometimes even reloading might not help. Instead use a section header and footer which has rounded edges as required and also which is inside the storyboard which will make the scrolling and loading of table view very smooth without any rendering issues ( sometimes called missing cells and appears on scroll )

refer here how to set the different integer values for rounded corners here : https://stackoverflow.com/questions/47699095/setting-masked-corners-in-interface-builder

Just use the above values for your section header and footer.

Solution 9 - Swift

create a UIVIEW inside cell's content view "backView" and add an outlet of backView to cell class then add these lines of code to awakeFromNib()

self.backView.layer.cornerRadius = 28
self.backView.clipsToBounds = true 

the corner radius depends on your design... the add these code to cellForRowAt function

cell.layer.shadowColor = UIColor.black.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.layer.shadowOpacity = 0.6

and set the cell view background color to clear color

Don't forget to add a little space between 4 sides of the cell and the backView you just added inside cell contentView in StoryBoard

hope you liked it

Solution 10 - Swift

Try this, it worked for me.

    cell.contentView.layer.cornerRadius = 5.0
    cell.contentView.layer.borderColor = UIColor.gray.withAlphaComponent(0.5).cgColor
    cell.contentView.layer.borderWidth = 0.5


    let border = CALayer()
    let width = CGFloat(2.0)
    border.borderColor = UIColor.darkGray.cgColor
    border.frame = CGRect(x: 0, y: cell.contentView.frame.size.height - width, width:  cell.contentView.frame.size.width, height: cell.contentView.frame.size.height)

    border.borderWidth = width
    cell.contentView.layer.addSublayer(border)
    cell.contentView.layer.masksToBounds = true
    cell.contentView.clipsToBounds = true

Solution 11 - Swift

It works without additional views!

override func awakeFromNib() {
    super.awakeFromNib()

    layer.masksToBounds = false
    layer.cornerRadius = Constants.cornerRadius
}

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    let layer = cell.layer
    layer.shadowOffset = CGSize(width: 0, height: 1)
    layer.shadowRadius = 2
    layer.shadowColor = UIColor.lightGray.cgColor
    layer.shadowOpacity = 0.2
    layer.frame = cell.frame
    cell.tagLabel.text = tagItems[indexPath.row].text

    return cell
}

Solution 12 - Swift

1- Create Custom TableViewCell Class . Paste the following code at class level right where you create IBOutlets. exerciseView is the view just inside ContentView to which you want to round.

@IBOutlet weak var exerciseView: UIView! {
        didSet {
            self.exerciseView.layer.cornerRadius = 15
            self.exerciseView.layer.masksToBounds = true
        }
    }

didSet is variable observer basically. You can do this in awakeFromNib function as well as:

self.exerciseView.layer.cornerRadius = 15
self.exerciseView.layer.masksToBounds = true

Solution 13 - Swift

If it's useful, I have been using the code below to achieve this, which only needs to be run in cellForRowAt.

First, add an extension to UITableViewCell to enable you to create a shadow and rounded corners on a TableViewCell:

extension UITableViewCell {
    func addShadow(backgroundColor: UIColor = .white, cornerRadius: CGFloat = 12, shadowRadius: CGFloat = 5, shadowOpacity: Float = 0.1, shadowPathInset: (dx: CGFloat, dy: CGFloat), shadowPathOffset: (dx: CGFloat, dy: CGFloat)) {
        layer.cornerRadius = cornerRadius
        layer.masksToBounds = true
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
        layer.shadowRadius = shadowRadius
        layer.shadowOpacity = shadowOpacity
        layer.shadowPath = UIBezierPath(roundedRect: bounds.insetBy(dx: shadowPathInset.dx, dy: shadowPathInset.dy).offsetBy(dx: shadowPathOffset.dx, dy: shadowPathOffset.dy), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
        layer.shouldRasterize = true
        layer.rasterizationScale = UIScreen.main.scale
        
        let whiteBackgroundView = UIView()
        whiteBackgroundView.backgroundColor = backgroundColor
        whiteBackgroundView.layer.cornerRadius = cornerRadius
        whiteBackgroundView.layer.masksToBounds = true
        whiteBackgroundView.clipsToBounds = false
        
        whiteBackgroundView.frame = bounds.insetBy(dx: shadowPathInset.dx, dy: shadowPathInset.dy)
        insertSubview(whiteBackgroundView, at: 0)
    }
}

Then just reference this in cellForRowAt:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! YourCellClass

    cell.addShadow(backgroundColor: .white, cornerRadius: 13, shadowRadius: 5, shadowOpacity: 0.1, shadowPathInset: (dx: 16, dy: 6), shadowPathOffset: (dx: 0, dy: 2))
 
    // Or if you are happy with the default values in your extension, just use this instead:
    // cell.addShadow()

    return cell
}

Here is the result for me:

Screenshot

Solution 14 - Swift

    cell.layer.cornerRadius = 0.25
    cell.layer.borderWidth = 0
    cell.layer.shadowColor = UIColor.black.cgColor
    cell.layer.shadowOffset = CGSize(width: 0, height: 0)
    cell.layer.shadowRadius = 5
    cell.layer.shadowOpacity = 0.1
    cell.layer.masksToBounds = false

Solution 15 - Swift

Let's Assume

  • viewContents = its the view which contain all your views
  • viewContainer = Its the view which contains viewContents with leading, trailing, top, bottom all are equal to zero.

Now the idea is, we are adding the shadow to the viewContainer. And rounding the corners of the viewContents. Most important don't forget to set background color of viewContainer to nil.

Here's the code snippet.

override func layoutSubviews() {
    super.layoutSubviews()
    //viewContainer is the parent of viewContents
    //viewContents contains all the UI which you want to show actually.
    
    self.viewContents.layer.cornerRadius = 12.69
    self.viewContents.layer.masksToBounds = true
    
    let bezierPath = UIBezierPath.init(roundedRect: self.viewContainer.bounds, cornerRadius: 12.69)
    self.viewContainer.layer.shadowPath = bezierPath.cgPath
    self.viewContainer.layer.masksToBounds = false
    self.viewContainer.layer.shadowColor = UIColor.black.cgColor
    self.viewContainer.layer.shadowRadius = 3.0
    self.viewContainer.layer.shadowOffset = CGSize.init(width: 0, height: 3)
    self.viewContainer.layer.shadowOpacity = 0.3
    
    // sending viewContainer color to the viewContents.
    let backgroundCGColor = self.viewContainer.backgroundColor?.cgColor
    //You can set your color directly if you want by using below two lines. In my case I'm copying the color.
    self.viewContainer.backgroundColor = nil
    self.viewContents.layer.backgroundColor =  backgroundCGColor
  }

Here's the result

enter image description here

Solution 16 - Swift

Since iOS 13 you just need to use the style "UITableViewStyleInsetGrouped".

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser3857868View Question on Stackoverflow
Solution 1 - SwiftR MoyerView Answer on Stackoverflow
Solution 2 - SwiftBorut TomazinView Answer on Stackoverflow
Solution 3 - SwiftSai kumar ReddyView Answer on Stackoverflow
Solution 4 - SwiftandreyView Answer on Stackoverflow
Solution 5 - SwiftposhaView Answer on Stackoverflow
Solution 6 - SwiftAshish VermaView Answer on Stackoverflow
Solution 7 - SwiftSergioPermView Answer on Stackoverflow
Solution 8 - SwiftMaxView Answer on Stackoverflow
Solution 9 - SwiftHelia FathiView Answer on Stackoverflow
Solution 10 - SwiftTom Derry Marion MantillaView Answer on Stackoverflow
Solution 11 - SwiftIvanPavliukView Answer on Stackoverflow
Solution 12 - SwiftAsad JamilView Answer on Stackoverflow
Solution 13 - SwiftBenView Answer on Stackoverflow
Solution 14 - Swiftislam magdyView Answer on Stackoverflow
Solution 15 - SwiftAmit KumarView Answer on Stackoverflow
Solution 16 - SwiftHermanView Answer on Stackoverflow