UIView with rounded corners and drop shadow?

311,960

Solution 1

The following code snippet adds a border, border radius, and drop shadow to v, a UIView:

// border radius
[v.layer setCornerRadius:30.0f];

// border
[v.layer setBorderColor:[UIColor lightGrayColor].CGColor];
[v.layer setBorderWidth:1.5f];

// drop shadow
[v.layer setShadowColor:[UIColor blackColor].CGColor];
[v.layer setShadowOpacity:0.8];
[v.layer setShadowRadius:3.0];
[v.layer setShadowOffset:CGSizeMake(2.0, 2.0)];

Swift 5 Version :

// border radius
v.layer.cornerRadius = 30.0

// border
v.layer.borderColor = UIColor.lightGray.cgColor
v.layer.borderWidth = 1.5

// drop shadow
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOpacity = 0.8
v.layer.shadowRadius = 3.0
v.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)

You can adjust the settings to suit your needs.

Also, add the QuartzCore framework to your project and:

#import <QuartzCore/QuartzCore.h>

See my other answer regarding masksToBounds.


Note

This may not work in all cases. If you find that this method interferes with other drawing operations that you are performing, please see this answer.

Solution 2

Swift

enter image description here

// corner radius
blueView.layer.cornerRadius = 10

// border
blueView.layer.borderWidth = 1.0
blueView.layer.borderColor = UIColor.black.cgColor

// shadow
blueView.layer.shadowColor = UIColor.black.cgColor
blueView.layer.shadowOffset = CGSize(width: 3, height: 3)
blueView.layer.shadowOpacity = 0.7
blueView.layer.shadowRadius = 4.0

Exploring the options

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

Problem 1: Shadow gets clipped off

What if there are sublayers or subviews (like an image) whose content we want to clip to the bounds of our view?

enter image description here

We can accomplish this with

blueView.layer.masksToBounds = true

(Alternatively, blueView.clipsToBounds = true gives the same result.)

enter image description here

But, oh no! The shadow was also clipped off because it's outside of the bounds! What to do? What to do?

Solution

Use separate views for the shadow and the border. The base view is transparent and has the shadow. The border view clips any other subcontent that it has to its borders.

// add the shadow to the base view
baseView.backgroundColor = UIColor.clear
baseView.layer.shadowColor = UIColor.black.cgColor
baseView.layer.shadowOffset = CGSize(width: 3, height: 3)
baseView.layer.shadowOpacity = 0.7
baseView.layer.shadowRadius = 4.0

// add the border to subview
let borderView = UIView()
borderView.frame = baseView.bounds
borderView.layer.cornerRadius = 10
borderView.layer.borderColor = UIColor.black.cgColor
borderView.layer.borderWidth = 1.0
borderView.layer.masksToBounds = true
baseView.addSubview(borderView)

// add any other subcontent that you want clipped
let otherSubContent = UIImageView()
otherSubContent.image = UIImage(named: "lion")
otherSubContent.frame = borderView.bounds
borderView.addSubview(otherSubContent)

This gives the following result:

enter image description here

Problem 2: Poor performance

Adding rounded corners and shadows can be a performance hit. You can improve performance by using a predefined path for the shadow and also specifying that it be rasterized. The following code can be added to the example above.

baseView.layer.shadowPath = UIBezierPath(roundedRect: baseView.bounds, cornerRadius: 10).cgPath
baseView.layer.shouldRasterize = true
baseView.layer.rasterizationScale = UIScreen.main.scale

See this post for more details. See here and here also.

This answer was tested with Swift 4 and Xcode 9.

Solution 3

Check out the example project on GitHub to make sure you use the component correctly.

Simple Swift 5 solution without any additional subviews or subclassing:

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

Note that you should set up your view with corner radius and other properties before calling addShadow.

After that, just call this from viewDidLoad like this:

button.addShadow(offset: CGSize.init(width: 0, height: 3), color: UIColor.black, radius: 2.0, opacity: 0.35)

Final result:

result

Super easy and simple!

Solution 4

One way to do this is to put the view with rounded corners in a view with the drop shadow.

UIView* roundedView = [[UIView alloc] initWithFrame: frame];
roundedView.layer.cornerRadius = 5.0;
roundedView.layer.masksToBounds = YES;

UIView* shadowView = [[UIView alloc] initWithFrame: frame];
shadowView.layer.shadowColor = [UIColor blackColor].CGColor;
shadowView.layer.shadowRadius = 5.0;
shadowView.layer.shadowOffset = CGSizeMake(3.0, 3.0);
shadowView.layer.shadowOpacity = 1.0;
[shadowView addSubview: roundedView];

Then you can add the shadowView wherever you want.

Solution 5

This worked for me. Trick was to move the background color from the main view to the layer.

CALayer *layer = view.layer;
layer.cornerRadius = 15.0f;
layer.masksToBounds = NO;

layer.shadowOffset = CGSizeMake(0, 3);
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowRadius = 2.0f;
layer.shadowOpacity = 0.35f;
layer.shadowPath = [[UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:layer.cornerRadius] CGPath];

CGColorRef  bColor = view.backgroundColor.CGColor;
view.backgroundColor = nil;
layer.backgroundColor =  bColor ;
Share:
311,960
Aditya Vaidyam
Author by

Aditya Vaidyam

Updated on July 21, 2022

Comments

  • Aditya Vaidyam
    Aditya Vaidyam almost 2 years

    I’ve been working on an application for a couple of years and received a simple design request: Round the corners on a UIView and add a drop shadow.To do as given below.

    I want a custom UIView... : I just wanted a blank white view with rounded corners and a light drop shadow (with no lighting effect). I can do each of those one by one but the usual clipToBounds/maskToBounds conflicts occur.

    enter image description here

  • Bhavin Bhadani
    Bhavin Bhadani about 7 years
    @Suragch so for only border, we need to use separate view or separate views for corner radius and shadow too?
  • Suragch
    Suragch about 7 years
    @EICaptainv2.0, If you only want a border (and/or corner radius) then you don't need a separate view. The separate view is for the situation where you need round corners and shadow.
  • Rutger Huijsmans
    Rutger Huijsmans about 7 years
    This isn't working for me. When I set the back ground color to clear on the baseView a shadow does not appear anymore. What am I doing wrong?
  • Cesare
    Cesare almost 7 years
    Does this work on buttons? Because it's not working on my end.
  • Hemang
    Hemang over 6 years
    I tried following the exact steps you suggested. But still no luck. It would be great if you share a sample (on Github) to see how you have done which seems impossible for me and other people.
  • trupin
    trupin over 6 years
    Managed to make it work only by removing this line layer.shadowPath = UIBezierPath.init(roundedRect: layer.bounds, cornerRadius: layer.cornerRadius).cgPath. Can't explain why though, does someone has an explanation for that?
  • Aleksander
    Aleksander over 6 years
    Not working, setting baseView.backgroundColor = UIColor.clear removes the shadow. Only if you set a background color will you see it.
  • Suragch
    Suragch over 6 years
    @Markus, I retested the code in my answer and it still worked with Swift 4 and Xcode 9.
  • Suragch
    Suragch over 6 years
    @Aleksander, I retested the code in my answer and it still worked with Swift 4 and Xcode 9. Even with the clear background color on the base view it still worked. I'm not sure why it wasn't working for you.
  • Mike Vosseller
    Mike Vosseller over 6 years
    FYI I was initially seeing the same problem that other commenters were seeing where the baseView's shadow not displaying when it's background color was clear. The problem was that I was only running the first part of the code (the baseView stuff). Once I added the borderView as a subview the shadow began displaying. Seems that for the shadow to display there must be at least one visible border (or background) in it's view hierarchy. So be sure to have borderView.layer.borderWidth >= 0 with a non-transparent borderView.layer.borderColor (or a non-transparent background color)
  • Satyendra Pandey
    Satyendra Pandey over 6 years
    @Suragch, I want to create rounded button with shadow effect so how can i add view in it ? If i am not wrong the click event will not be generated if view is added on button as subview ?
  • Ben Thomas
    Ben Thomas about 6 years
    The expression is "fool proof". :)
  • Ben Thomas
    Ben Thomas about 6 years
    I was just correcting the English. :) The solution works.
  • inexcitus
    inexcitus almost 6 years
    Like every other "solution" in this thread, it simply doesn't work, at least not on iOS 11.0 / Swift 4.1.
  • Baptiste Mille-Mathias
    Baptiste Mille-Mathias over 5 years
    Hi, thanks for your answer, you should add some comments to your code to explain a bit as described in How to Answer.
  • Thomás Pereira
    Thomás Pereira over 5 years
    Did you read "Swift 3" in the begin of the thread? So, it means that is a Swift 3 solution, I didn't test it in Swift 4.1 because I don't need it anymore. Feel free to edit the answer and give a solution. ;) Cheers
  • Sergey Grischyov
    Sergey Grischyov over 5 years
    @Curnelious feel free to take a look at the updated answer with an Xcode project example. It cannot not work :)
  • Martin
    Martin about 5 years
    If you are using autolayout, a layoutIfNeeded() might be necessary before drawing the shadow path.
  • D. Pratt
    D. Pratt almost 5 years
    This is one of the best explanations I have come across on SO. Great job, and thank you so much!
  • Rishabh
    Rishabh over 4 years
    This worked for me too, just need to one thing more is to make all the subviews background-color to clear so that only container view has a visible background and this resolved my problem. Thanks!! @SergeyGrischyov
  • Andy Weinstein
    Andy Weinstein about 3 years
    Thanks! This worked for me when I followed the example in GitHub closely. It requires a UIButton, and updating params and calling the addShadow function from viewDidLoad. I am wondering if it would be possible to generalize this into an IDesignable class based on UIView, which would not require any of the additional additionalization from outside (viewDidLoad).
  • Leon
    Leon almost 3 years
    This is a great solution which I have been using for a while, however it's worth recognising that this will stop your view's background colour updating for dar mode automatically. This can be fixed by manually setting the layer.backgroundColor in traitCollectionDidChange.
  • Xys
    Xys over 2 years
    Won't work if you have both a borderWidth and a shadow
  • Xys
    Xys over 2 years
    Wrong solution, you shouldn't add layers inside your layoutSubviews method
  • Xys
    Xys over 2 years
    This doesn't work since borderWidth requires masksToBounds to be true but shadows to be false