450

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

mfaani
  • 33,269
  • 19
  • 164
  • 293
Aditya Vaidyam
  • 6,259
  • 3
  • 24
  • 26
  • 1
    Since you say in a comment below that you got this working using CoreGraphics, would you mind sharing the answer with the community so that you can help others in the same situation, as they tried to help you? – lnafziger Jun 12 '13 at 14:34
  • I'm sorry, this was quite a long time ago, and I don't have the source anymore. What I did was override -drawRect: and use UIBezierPath to draw a rectangle, and apply a shadow to the layer backing the view... if I remember correctly. :) – Aditya Vaidyam Jun 25 '13 at 04:51
  • 6
    The accepted answer does not work! – onmyway133 Oct 12 '14 at 14:09
  • Swift 3 & IBInspectable solution. ;) http://stackoverflow.com/a/43958505/3052059 – Thomás Pereira May 13 '17 at 22:19
  • 1
    Possible duplicate of [Giving UIView rounded corners](http://stackoverflow.com/questions/1509547/giving-uiview-rounded-corners) –  May 18 '17 at 13:12
  • 2
    @Sachavijay You should verify the dates of both posts before you comment. – Aditya Vaidyam May 18 '17 at 16:48
  • This is an way How can to do it. https://stackoverflow.com/a/45570428/3532177 – Douglas Queiroz Aug 08 '17 at 14:21
  • Watch https://stackoverflow.com/questions/1177775/how-is-the-relation-between-uiviews-clipstobounds-and-calayers-maskstobounds – Nike Kov Mar 23 '20 at 15:02

35 Answers35

748

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.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • @Suragch so for only border, we need to use separate view or separate views for corner radius and shadow too? – Bhavin Bhadani Apr 21 '17 at 11:58
  • 1
    @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*. – Suragch Apr 21 '17 at 12:36
  • 3
    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? – Rutger Huijsmans Apr 24 '17 at 06:06
  • 4
    Not working, setting `baseView.backgroundColor = UIColor.clear` removes the shadow. Only if you set a background color will you see it. – Aleksander Oct 16 '17 at 23:23
  • @Markus, I retested the code in my answer and it still worked with Swift 4 and Xcode 9. – Suragch Dec 06 '17 at 06:37
  • @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. – Suragch Dec 08 '17 at 02:51
  • 4
    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) – Mike Vosseller Jan 08 '18 at 15:00
  • @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 ? – Satyendra Pandey Jan 27 '18 at 09:37
  • 1
    If you are using autolayout, a `layoutIfNeeded()` might be necessary before drawing the shadow path. – Martin Apr 07 '19 at 05:34
  • This is one of the best explanations I have come across on SO. Great job, and thank you so much! – D. Pratt Jul 01 '19 at 18:04
464

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.

mehdigriche
  • 444
  • 1
  • 4
  • 14
Evan Mulawski
  • 54,662
  • 15
  • 117
  • 144
  • 90
    Well the problem with it is that when I set corner radius, it sets maskToBounds: YES, whereas the shadow requires clipToBounds: NO (where clipToBounds does the same as maskToBounds) – Aditya Vaidyam Jan 21 '11 at 02:37
  • 15
    same problem here. If I have a background color I want that to be clipped to the rounded corners. To do that I have to use maskToBounds=TRUE, but then the shadow dissapears.. – hfossli Mar 23 '11 at 10:35
  • 3
    For newbies like me: I had to import the QuartzCore framework into my project in order to call methods on the layer object. – SilithCrowe Jan 27 '12 at 21:55
  • 2
    Note for people concerned with performance: This is not a fast operation. If you wanted to add a drop shadow to a button in every row of a table view for example, it would have the potential to cause some sluggish transitions and scrolling. Pre-rendering the shadow as an image and using that would be much more efficient. – lindon fox Jul 25 '12 at 02:40
  • As several have pointed out, this doesn't work. Just set the view's background color to red and a corner radius greater than 2.f and you'll see. – Pascal Nov 30 '12 at 19:37
  • 1
    It does work, but it has limitations. For example if you have another UIView near either of the corners, it will cover the rounding unless it's transparent. I just solved this in a very crude way by adding rounded corners to the inner view and putting a cover over its other edge to get the effect. Not ideal. Wish Apple would fix. – Kalle Jan 02 '13 at 12:18
  • @Kalle That's why I said it doesn't work if you e.g. have a red background color for the view. It's a hack that works for some if not most use cases, but it's not the right answer technically. – Pascal Jul 19 '13 at 16:32
  • Hi, this is working for me, if I'm creating a UIView through code(obviously masksToBounds is false). But, when I'm trying to use the above code on a UIView that is created from nib, only one works at a time. ie, shadow and corner radius doesn't work together. Please help – Suran Nov 28 '13 at 08:30
  • 42
    The way to get this to work *the right way* is to use an inner container view, which will house your border and your background color, both with a corner radius. This view will be clipped to bounds! The second, outer container view will house the first one, has the same frame, with just a drop shadow. I've done this quite a few times to combine a border, drop shadow, and corner radius. It's really annoying, but it works really well. – Kpmurphy91 Dec 09 '13 at 19:10
  • 29
    Doesn't work. No idea why there are so many up votes. Was this applicable in older versions? – Yarneo May 27 '14 at 18:48
  • @EvanMulawski Im using 7.1. The maskToBounds when its off makes the rounded corner not applicable, and when its on, theres no shadow, as its outside the view. – Yarneo May 28 '14 at 18:09
  • 1
    @EvanMulawski, if you do maskToBounds as true, you will be stopping any kind of drop shadow to clip outside of the view's bounds. So I don't understand how you got a successful effect. – Yarneo Feb 14 '15 at 01:44
  • 2
    This doesn't work. The only way to do what the OP is asking is to use two views as @Kpmurphy91 stated. – c.dunlap Apr 14 '16 at 19:21
  • Wrong Answer. border was drawn but corners not not mask. – Sabrina Dec 02 '18 at 19:04
  • Why does this work? it definitely worked for me, but i thought that if you add a shadow, the corners won't respect the cornerRadius.... Is it becuase of the quartzcore of someting? – Travis Delly Mar 29 '19 at 00:18
  • Worked for me in Xamarin.iOS. Thanks a lot! I also made borderRadius = 200 so I have nice tint around my view – S. Koshelnyk Apr 10 '19 at 23:16
  • 1
    for those wondering, you have to declare the corner radius FIRST then you can declare the shadow and they both work together, if you do it the other way around the corner radius code ends up clipping the shadow. It seems like the order would not matter, I too was surprised when it did indeed work when setting the corners first. – Albert Renshaw Jul 26 '19 at 21:48
  • Hi again, my above comment seems to not explain the full picture. This works on view with just a backgroundColor for example, but on views with sub content (e.g. a UIImageView and its image) it won't clip the sub-content. So this will work with a UIView but not a UIImageView which explains why some comments/upvotes are in favor of it while others aren't. – Albert Renshaw Jun 01 '20 at 06:29
  • why is it approved answer? The question contains `clipsToBounds` which won't work – Vyachaslav Gerchicov Sep 30 '20 at 06:03
102

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!

Sergey Grischyov
  • 11,995
  • 20
  • 81
  • 120
  • Does this work on buttons? Because it's not working on my end. – Cesare Jun 22 '17 at 17:26
  • 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. – Hemang Aug 31 '17 at 10:27
  • 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? – trupin Sep 07 '17 at 20:58
  • @Curnelious feel free to take a look at the updated answer with an Xcode project example. It cannot not work :) – Sergey Grischyov Nov 15 '18 at 11:07
  • 3
    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 – Rishabh Sep 04 '19 at 04:23
  • 1
    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). – Andy Weinstein May 02 '21 at 10:28
  • 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`. – Leon Aug 09 '21 at 13:26
80

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.

Softlion
  • 12,281
  • 11
  • 58
  • 88
David C.
  • 817
  • 6
  • 5
  • 7
    Amit, you have to set maskToBounds/clipToBounds = YES for the * roundedView only *. DO NOT set this to the shadowView. I have not tried the above code but know for sure that this solution definitely works though not ideal. The higher shadowRadius takes care of the corner radius areas. Set the shadowRadius to 0 or 1 and you will notice what I am trying to say. – Deepak G M Feb 15 '13 at 06:50
  • 2
    Something like shadowView.layer.shadowOpacity = 0.6; is missing – Bo. Mar 17 '13 at 18:25
  • 3
    "shadowView.layer.opacity = 1.0" should be "shadowView.layer.shadowOpacity = 1.0" – Chris Sep 04 '14 at 04:55
  • Works on iOS 9 if use shadowView.layer.shadowOpacity = 1.0 – Ruslan Mansurov Oct 19 '15 at 16:13
  • Code fixed for shadowOpacity – Softlion Dec 03 '15 at 13:19
43

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 ;
JOM
  • 8,139
  • 6
  • 78
  • 111
Ade
  • 631
  • 5
  • 8
  • Although all the other solutions work, and perhaps they are more general, this is by far the best solution to the problem. Adding subviews or sublayers create a world or pain trying to maintain the frame sizes, or, at best, may cause performance issues. – emem Oct 16 '16 at 15:09
  • This should be the answer. Clean and elegant. – Axy Dec 06 '16 at 03:54
  • Best solution and definitely elegant! – Roberto Ferraz Dec 20 '16 at 09:09
  • Wow, this actually works. I don't understand why it should work -- you'd think the view's backgroundColor properly would directly map to the layer.backgroundColor property on iOS -- but it DOES WORK. (Xcode 8, Swift 3.) Well done, and thanks. This should be the accepted answer. – Womble Feb 27 '17 at 02:43
  • I've created a Swift 3.1 version of your answer using ```UIView extension``` here - http://stackoverflow.com/a/43295741/1313939 thanks for the inspiration! – Sergey Grischyov Apr 08 '17 at 15:11
  • You inspired me, I finally got It working! Thanks you. I did an extension to integrate IBInspectable your solution. http://stackoverflow.com/a/43958505/3052059 – Thomás Pereira May 13 '17 at 22:20
  • The only way I found which works for me is to remove this line `layer.shadowPath = [[UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:layer.cornerRadius] CGPath];`. Any idea why? – trupin Sep 07 '17 at 21:07
  • if UIView is having subViews extreme at the margin, it fails. – umakanta Jan 31 '18 at 09:10
  • Not sure why, but it is not working for me. Turning off clips to bounds prevents the corner radius from working in a filled UIButton. (i.e. not a border, a fill). The shadows work, but the corners do not. – Andrew Kinnie Nov 14 '18 at 05:30
27

I solved the problem using the following trick when assigning shadow path for the container view :

[UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:12]

Notice that the path given to the shadow is a rounded rectangle with the same corner radius as the background that the cell contains:

//this is the border for the UIView that is added to a cell
cell.backgroundView.layer.cornerRadius = 12;
cell.backgroundView.layer.masksToBounds = YES;
cell.backgroundView.layer.borderColor = [UIColor darkGrayColor].CGColor;
cell.backgroundView.layer.borderWidth = 1;

//this is the shadow around the cell itself (cannot have round corners with borders and shadow, need to use two views
cell.layer.shadowRadius = 2;
cell.layer.cornerRadius = 12;
cell.layer.masksToBounds = NO;
[[cell layer] setShadowColor:[[UIColor darkGrayColor] CGColor]];

[[cell layer] setShadowOffset:CGSizeMake(0.0,0.0)];
[[cell layer] setShadowOpacity:1.0];

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:12];
[[cell layer] setShadowPath:[path CGPath]];
Axel Guilmin
  • 11,454
  • 9
  • 54
  • 64
Alex Stone
  • 46,408
  • 55
  • 231
  • 407
17

If you are struggling because of the rounded corners vs. subviews vs. masksToBounds, then try using my function:

- (UIView*)putView:(UIView*)view insideShadowWithColor:(UIColor*)color andRadius:(CGFloat)shadowRadius andOffset:(CGSize)shadowOffset andOpacity:(CGFloat)shadowOpacity
{
    CGRect shadowFrame; // Modify this if needed
    shadowFrame.size.width = 0.f;
    shadowFrame.size.height = 0.f;
    shadowFrame.origin.x = 0.f;
    shadowFrame.origin.y = 0.f;
    UIView * shadow = [[UIView alloc] initWithFrame:shadowFrame];
    shadow.userInteractionEnabled = NO; // Modify this if needed
    shadow.layer.shadowColor = color.CGColor;
    shadow.layer.shadowOffset = shadowOffset;
    shadow.layer.shadowRadius = shadowRadius;
    shadow.layer.masksToBounds = NO;
    shadow.clipsToBounds = NO;
    shadow.layer.shadowOpacity = shadowOpacity;
    [view.superview insertSubview:shadow belowSubview:view];
    [shadow addSubview:view];
    return shadow;
}

call it on your view. whether your view has rounded corners, no matter its size, its shape - a nice shadow will be drawn.

Just keep the return value of the function so you can refer to it when you want to remove the table (or for example use insertSubview:aboveView:)

KlimczakM
  • 12,576
  • 11
  • 64
  • 83
daniel.gindi
  • 3,457
  • 1
  • 30
  • 36
  • it works fine. But if view has gesture recognizers, then it will not work. how can we solve it? – manujmv May 06 '13 at 10:03
  • @manujmv Do you see the lines where "// Modify this if needed" is specified? That's what you need. shadow.userInteractionEnabled = YES; – daniel.gindi May 06 '13 at 11:45
  • @manujmv then you should test the frames of the view and subview to see why. something probably is not right there. this exact code works for me in some very nice apps – daniel.gindi May 06 '13 at 15:38
  • 2
    This solution works great for UITableViews with rounded corners. Wish I could give it more up votes. Thanks! – Chris Hart May 11 '13 at 22:17
  • @CarlosEduardoLópez Do you see the `shadow.userInteractionEnabled = NO; // Modify this if needed` line? So this is the case where needed. `userInteractionEnabled` is a basic and popular property you should be already familiar with :-) – daniel.gindi Aug 28 '14 at 06:52
  • I'm familiar with that, but that's not the problem. The problem is the CGRect position. If you change the background, you will notice the views are in the top corner, so you can enable the interaction, but they won't pass the gestures cause they're not in the same position – Carlos López Aug 28 '14 at 14:55
  • Gestures are usually working fine with this, at least for me. You situation is probably different in an important manner – daniel.gindi Aug 28 '14 at 18:06
  • Basically, I didn't pay attention to the comment in the line where CGRect shadowFrame is declared, that's why this solution didn't work with UIButton & UITableView. I just did shadowFrame = view.frame and erased those lines: shadowFrame.size.width = 0.f; shadowFrame.size.height = 0.f; shadowFrame.origin.x = 0.f; shadowFrame.origin.y = 0.f; Carlos Eduardo López solved and answered here: http://stackoverflow.com/questions/25539877/cant-click-on-uibutton-after-add-a-subview – Max Hiroyuki Ueda Apr 19 '16 at 19:06
15

Using Swift 4 and Xcode 9, this is a working example of rounding an ImageView with a drop shadow, and a border.

    //set dimensions and position of image (in this case, centered)
    let imageHeight: CGFloat = 150, imageWidth: CGFloat = 150
    let xPosition = (self.view.frame.width / 2) - (imageWidth / 2)
    let yPosition = (self.view.frame.height / 2) - (imageHeight / 2)

    //set desired corner radius
    let cornerRadius: CGFloat = 20

    //create container for the image
    let imageContainer = UIView(frame: CGRect(x: xPosition, y: yPosition, width: imageWidth, height: imageHeight))

    //configure the container
    imageContainer.clipsToBounds = false
    imageContainer.layer.shadowColor = UIColor.black.cgColor
    imageContainer.layer.shadowOpacity = 1
    imageContainer.layer.shadowOffset = CGSize(width: 3.0, height: 3.0)
    imageContainer.layer.shadowRadius = 5
    imageContainer.layer.shadowPath = UIBezierPath(roundedRect: imageContainer.bounds, cornerRadius: cornerRadius).cgPath

    //create imageView
    let imageView = UIImageView(frame: imageContainer.bounds)

    //configure the imageView
    imageView.clipsToBounds = true
    imageView.layer.cornerRadius = cornerRadius
    //add a border (if required)
    imageView.layer.borderColor = UIColor.black.cgColor
    imageView.layer.borderWidth = 1.0
    //set the image
    imageView.image = UIImage(named: "bird")

    //add the views to the superview
    view.addSubview(imageContainer)
    imageContainer.addSubview(imageView)

enter image description here

If you want the image to be circular: (and shown without border)

let cornerRadius = imageWidth / 2

enter image description here

rbaldwin
  • 4,581
  • 27
  • 38
7

You need to use use shadowView and roundView

enter image description here

shadowView

  • Must has background color
  • Should lay behind roundView
  • The trick is to layout shadowView a bit inside, and its shadow needs to glow out. Adjust the insets so that shadowView is completely invisible behind roundView

roundView

  • Must clips subviews

The code

addSubviews(shadowView, roundView)
roundView.addSubviews(titleLabel, subtitleLabel, imageView)

// need inset
shadowView.pinEdges(view: self, inset: UIEdgeInsets(constraintInsets: 2))
roundView.pinEdges(view: self)

do {
  shadowView.backgroundColor = .white // need background
  let layer = shadowView.layer
  layer.shadowColor = UIColor.black.cgColor
  layer.shadowRadius = 3
  layer.shadowOffset = CGSize(width: 3, height: 3)
  layer.shadowOpacity = 0.7
  layer.shouldRasterize = true
}

do {
  roundView.backgroundColor = .white
  let layer = roundView.layer
  layer.masksToBounds = true
  layer.cornerRadius = 5
}

Or you can just do below without specifying clipToBounds/maskToBounds

layer.shadowColor = UIColor.gray.cgColor
layer.shadowOffset = CGSize(width: 3, height: 3)
layer.shadowOpacity = 0.8
onmyway133
  • 45,645
  • 31
  • 257
  • 263
7

Here is the solution that will work for sure!

I have created UIView extension with required edges to apply shadow on as below


enum AIEdge:Int {
    case
    Top,
    Left,
    Bottom,
    Right,
    Top_Left,
    Top_Right,
    Bottom_Left,
    Bottom_Right,
    All,
    None
}

extension UIView {
        
    func applyShadowWithCornerRadius(color:UIColor, opacity:Float, radius: CGFloat, edge:AIEdge, shadowSpace:CGFloat, cornerRadius: CGFloat)    {

        var sizeOffset:CGSize = CGSize.zero
        
        switch edge {
        case .Top:
            sizeOffset = CGSize(width: 0, height: -shadowSpace)
        case .Left:
            sizeOffset = CGSize(width: -shadowSpace, height: 0)
        case .Bottom:
            sizeOffset = CGSize(width: 0, height: shadowSpace)
        case .Right:
            sizeOffset = CGSize(width: shadowSpace, height: 0)
            
            
        case .Top_Left:
            sizeOffset = CGSize(width: -shadowSpace, height: -shadowSpace)
        case .Top_Right:
            sizeOffset = CGSize(width: shadowSpace, height: -shadowSpace)
        case .Bottom_Left:
            sizeOffset = CGSize(width: -shadowSpace, height: shadowSpace)
        case .Bottom_Right:
            sizeOffset = CGSize(width: shadowSpace, height: shadowSpace)
            
            
        case .All:
            sizeOffset = CGSize(width: 0, height: 0)
        case .None:
            sizeOffset = CGSize.zero
        }

        self.layer.cornerRadius = cornerRadius
        self.layer.masksToBounds = true

        self.layer.shadowColor = color.cgColor
        self.layer.shadowOpacity = opacity
        self.layer.shadowOffset = sizeOffset
        self.layer.shadowRadius = radius
        self.layer.masksToBounds = false

        self.layer.shadowPath = UIBezierPath(roundedRect:self.bounds, cornerRadius:self.layer.cornerRadius).cgPath
    }
}

Finally, you can call the shadow function as below for any of your UIView subclass, you can also specify the edge to apply shadow on, try different variations as per your need changing parameters of below method call.

viewRoundedToBeShadowedAsWell.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)

NOTE: If still that doesn't work, try calling it from MAIN THREAD

DispatchQueue.main.async {
   viewRoundedToBeShadowedAsWell.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
}

Hope someone finds this useful !

Below is the result images:

enter image description here

enter image description here

enter image description here

Dhaval H. Nena
  • 3,992
  • 1
  • 37
  • 50
6

I've created a helper on UIView

@interface UIView (Helper)

- (void)roundCornerswithRadius:(float)cornerRadius
               andShadowOffset:(float)shadowOffset;
@end

you can call it like this

[self.view roundCornerswithRadius:5 andShadowOffset:5];

Here's the implementation

- (void)roundCornerswithRadius:(float)cornerRadius
               andShadowOffset:(float)shadowOffset
{
    const float CORNER_RADIUS = cornerRadius;
    const float SHADOW_OFFSET = shadowOffset;
    const float SHADOW_OPACITY = 0.5;
    const float SHADOW_RADIUS = 3.0;

    UIView *superView = self.superview;

    CGRect oldBackgroundFrame = self.frame;
    [self removeFromSuperview];

    CGRect frameForShadowView = CGRectMake(0, 0, oldBackgroundFrame.size.width, oldBackgroundFrame.size.height);
    UIView *shadowView = [[UIView alloc] initWithFrame:frameForShadowView];
    [shadowView.layer setShadowOpacity:SHADOW_OPACITY];
    [shadowView.layer setShadowRadius:SHADOW_RADIUS];
    [shadowView.layer setShadowOffset:CGSizeMake(SHADOW_OFFSET, SHADOW_OFFSET)];

    [self.layer setCornerRadius:CORNER_RADIUS];
    [self.layer setMasksToBounds:YES];

    [shadowView addSubview:self];
    [superView addSubview:shadowView];

}
Zayin Krige
  • 3,229
  • 1
  • 35
  • 34
  • 2
    This is a good elegant solution. Ensure that your view has been added to its superview before use. I added some parameters to give me more control over the shadow, but overall works perfect. Thanks! – Aaron Vegh Jan 06 '15 at 15:47
  • 1
    This is nice solution but it doesn't work with autolayout: the view will be drawn on origin 0,0 – gderaco Feb 02 '17 at 18:00
5

After one whole day research of the round corner view with shadow, I'm glad to post my custom uiview class here, hope to end this question:

RoundCornerShadowView.h

#import <UIKit/UIKit.h>

@interface RoundCornerShadowView : UIView

@end

RoundCornerShadowView.m

#import "RoundCornerShadowView.h"

@implementation RoundCornerShadowView

// *** must override this method, not the other method ***
// otherwise, the background corner doesn't disappear....
// @2015/05/29
-(void) layoutSubviews {
    [super layoutSubviews];//is must to ensure rightly layout children view

    //1. first, create Inner layer with content
    CALayer *innerView = [CALayer layer];
    innerView.frame = CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height);
    //instead of: innerView.frame = self.frame;
    innerView.borderWidth = 1.0f;
    innerView.cornerRadius = 6.0f;
    innerView.masksToBounds = YES;
    innerView.borderColor = [[UIColor lightGrayColor] CGColor];
    innerView.backgroundColor = [[UIColor whiteColor] CGColor];
    //put the layer to the BOTTOM of layers is also a MUST step...
    //otherwise this layer will overlay the sub uiviews in current uiview...
    [self.layer insertSublayer:innerView atIndex:0];

    //2. then, create shadow with self layer
    self.layer.masksToBounds = NO;
    self.layer.shadowColor = [[UIColor darkGrayColor] CGColor];
    self.layer.shadowOpacity = 0.4f;
    //shadow length
    self.layer.shadowRadius = 2.0f;
    //no offset
    self.layer.shadowOffset = CGSizeMake(0, 0);
    //right down shadow
    //[self.layer setShadowOffset: CGSizeMake(1.0f, 1.0f)];

    //3. last but important, MUST clear current view background color, or the color will show in the corner!
    self.backgroundColor = [UIColor clearColor];
}

@end

so, NO need to add subview in view or below in target view, just add one layer in current view, and do 3 step to complete it!

take a close look at to the comments in the code, it's helpful to understanding the component!

lwz7512
  • 81
  • 1
  • 2
5

Swift 3 & IBInspectable solution:
Inspired by Ade's solution

First, create an UIView extension:

//
//  UIView-Extension.swift
//  

import Foundation
import UIKit

@IBDesignable
extension UIView {
     // Shadow
     @IBInspectable var shadow: Bool {
          get {
               return layer.shadowOpacity > 0.0
          }
          set {
               if newValue == true {
                    self.addShadow()
               }
          }
     }

     fileprivate func addShadow(shadowColor: CGColor = UIColor.black.cgColor, shadowOffset: CGSize = CGSize(width: 3.0, height: 3.0), shadowOpacity: Float = 0.35, shadowRadius: CGFloat = 5.0) {
          let layer = self.layer
          layer.masksToBounds = false

          layer.shadowColor = shadowColor
          layer.shadowOffset = shadowOffset
          layer.shadowRadius = shadowRadius
          layer.shadowOpacity = shadowOpacity
          layer.shadowPath = UIBezierPath(roundedRect: layer.bounds, cornerRadius: layer.cornerRadius).cgPath

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


     // Corner radius
     @IBInspectable var circle: Bool {
          get {
               return layer.cornerRadius == self.bounds.width*0.5
          }
          set {
               if newValue == true {
                    self.cornerRadius = self.bounds.width*0.5
               }
          }
     }

     @IBInspectable var cornerRadius: CGFloat {
          get {
               return self.layer.cornerRadius
          }

          set {
               self.layer.cornerRadius = newValue
          }
     }


     // Borders
     // Border width
     @IBInspectable
     public var borderWidth: CGFloat {
          set {
               layer.borderWidth = newValue
          }

          get {
               return layer.borderWidth
          }
     }

     // Border color
     @IBInspectable
     public var borderColor: UIColor? {
          set {
               layer.borderColor = newValue?.cgColor
          }

          get {
               if let borderColor = layer.borderColor {
                    return UIColor(cgColor: borderColor)
               }
               return nil
          }
     }
}

Then, simply select your UIView in interface builder setting shadow ON and corner radius, like below:

Selecting your UIView

Setting shadow ON & corner radius

The result!

Result

Cesare
  • 9,139
  • 16
  • 78
  • 130
Thomás Pereira
  • 9,589
  • 2
  • 31
  • 34
  • Like every other "solution" in this thread, it simply doesn't work, at least not on iOS 11.0 / Swift 4.1. – inexcitus Jun 17 '18 at 17:58
  • 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 – Thomás Pereira Oct 20 '18 at 13:11
  • Won't work if you have both a borderWidth and a shadow – Xys Jan 25 '22 at 17:12
5

Something swifty tested in swift 4

import UIKit

extension UIView {
    @IBInspectable var dropShadow: Bool {
        set{
            if newValue {
                layer.shadowColor = UIColor.black.cgColor
                layer.shadowOpacity = 0.4
                layer.shadowRadius = 1
                layer.shadowOffset = CGSize.zero
            } else {
                layer.shadowColor = UIColor.clear.cgColor
                layer.shadowOpacity = 0
                layer.shadowRadius = 0
                layer.shadowOffset = CGSize.zero
            }
        }
        get {
            return layer.shadowOpacity > 0
        }
    }
}

Produces

enter image description here

If you enable it in the Inspector like this:

enter image description here

It will add the User Defined Runtime Attribute, resulting in:

enter image description here

(I added previously the cornerRadius = 8)

:)

dGambit
  • 531
  • 3
  • 11
5

iOS shadow and cornerRadius

[iOS CALayer]

[iOS masksToBounds]

[iOS debug rendering]

You are able to setup shadow using layer

//shadow
view1.layer.shadowColor = UIColor.magenta.cgColor

view1.layer.shadowOffset = CGSize(width: 0, height: 0)
view1.layer.shadowOpacity = 1
view1.layer.shadowRadius = 5

//cornerRadius
view1.layer.cornerRadius = 5

Visualization

1.shadowOffset.width, 2.shadowOffset.height, 3.shadowOpacity, 4. shadowRadius

  • shadowOffset width and height are any
  • shadowOpacity is from 0 to 1
  • shadowRadius is positive from 0

Not simple task

Please note that shadow is not calculated only based on boundaries and cornerRaduis. During creating shadows next items are taken into account:

  • subviews layers
  • sublayers
  • content(Backing Image)
view1.backgroundColor = .clear
view1.layer.contents = UIImage(named: "ring")?.cgImage
view1.layer.contentsScale = UIScreen.main.scale

ScaleFactor(contentsScale, rasterizationScale) - by default is 1.0

currentBitmapSize = layerSize * scaleFactor

//non retina
1 point == 1x pixels

//Retina
1 point == 2x pixels
//or
1 point == 3x pixels

//For example to draw line 
point(width: 4, height: 2) == 1x pixels(width: 4, height: 2)(8 pixels) == 2x pixels(width: 8, height: 4)(32 pixels)

Use UIScreen.main.scale = scale factor of current screen

[iOS pixels vs points vs units]

Perfomance

Using layer.cornerRadius, shadow has some performance impact

As for layer.cornerRadius perfomance:

Applying it color blending is used [Read more]

As for shadow Xcode hint you to:

The layer is using dynamic shadows which are expensive to render. If possible try setting shadowPath, or pre-rendering the shadow into an image and putting it under the layer

1. Additionally use shadowPath

For internally static layer. By default it is nil that is why UIKit should create view off-screen and based on this information create a shadow. That is why you are able to predefine a path and set it. One more advantage is that you are able to create a custom shadow as you wish

view1.layer.shadowPath = UIBezierPath(roundedRect: view1.bounds, cornerRadius: 50).cgPath

Disadvantage - not dynamic. If view changes boundaries(width, height, cornerRadius...) the shadow remains as it was(old boundaries). If view's position is changed(moved, scrolled) shadowPath will be correct

2. Cache Rasterize

[iOS shouldRasterize]

Notes:

  • To hide shadow you can just set view.layer.masksToBounds = true

  • When view doesn't have a backgroundColor (clear/transparent) - shadow is applied for all subviews. Official doc

  • UITableView where every section has a shadow:

    • UITableView in Grouped or Inset Grouped Style
    • UITableView backgroundColor is clear
public func applyShadowForGroupedTableView(
  _ tableView: UITableView,
  shadowColor: CGColor,
  shadowOpacity: Float,
  shadowRadius: CGFloat,
  shadowOffset: CGSize
) {
  tableView.layer.masksToBounds = false
  tableView.layer.shadowColor = shadowColor
  tableView.layer.shadowOpacity = shadowOpacity
  tableView.layer.shadowRadius = shadowRadius
  tableView.layer.shadowOffset = shadowOffset
  tableView.layer.backgroundColor = UIColor.clear.cgColor
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
3

Here is the solution for masksToBounds conflict problem, it works for me.

After you set the corderRadius/borderColor/shadow and so on, set masksToBounds as NO:

v.layer.masksToBounds = NO;
3

Shadow + Border + Corner Radius enter image description here

    scrollview.backgroundColor = [UIColor whiteColor]; 
    CALayer *ScrlViewLayer = [scrollview layer];
    [ScrlViewLayer setMasksToBounds:NO ];
    [ScrlViewLayer setShadowColor:[[UIColor lightGrayColor] CGColor]];
    [ScrlViewLayer setShadowOpacity:1.0 ];
    [ScrlViewLayer setShadowRadius:6.0 ];
    [ScrlViewLayer setShadowOffset:CGSizeMake( 0 , 0 )];
    [ScrlViewLayer setShouldRasterize:YES];
    [ScrlViewLayer setCornerRadius:5.0];
    [ScrlViewLayer setBorderColor:[UIColor lightGrayColor].CGColor];
    [ScrlViewLayer setBorderWidth:1.0];
    [ScrlViewLayer setShadowPath:[UIBezierPath bezierPathWithRect:scrollview.bounds].CGPath];
Darshit Shah
  • 2,366
  • 26
  • 33
3

Here is my version in Swift 3 for a UIView

let corners:UIRectCorner = [.bottomLeft, .topRight]
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()

mask.path = path.cgPath
mask.fillColor = UIColor.white.cgColor

let shadowLayer = CAShapeLayer()
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 4.0)
shadowLayer.shadowRadius = 6.0
shadowLayer.shadowOpacity = 0.25
shadowLayer.shadowPath = mask.path

self.layer.insertSublayer(shadowLayer, at: 0)
self.layer.insertSublayer(mask, at: 1)
Kurt Van den Branden
  • 11,995
  • 10
  • 76
  • 85
Yung Dai
  • 158
  • 1
  • 5
3

Swift 4 : Create Subclass of UIView

class ShadowView: UIView {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // corner radius
        self.layer.cornerRadius = 10

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

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

}

Using..

Use Class Shadow View

benmore99
  • 903
  • 9
  • 17
  • This doesn't work since `borderWidth` requires `masksToBounds` to be `true` but shadows to be `false` – Xys Jan 25 '22 at 17:20
2

Well if you don't want to change your nibs and view hierarchy as suggested David C. this method will do it for you. To add rounded corners and shadow to your UIImageView just use this method, for example:

[Utils roundCornersForImageView:myImageView withCornerRadius:6.0 
andShadowOffset:2.0];

(!) For performance reasons I don't think it is a good idea to use this code in something like UITableView, since this code changes view hierarchy. So I will suggest to change your nib and add a container view for shadow effect and use Davic C. code.

+ (void)roundCornersForImageView:(UIImageView *)imageView 
withCornerRadius:(float)cornerRadius andShadowOffset:(float)shadowOffset
{
    const float CORNER_RADIUS = cornerRadius;
    const float BORDER_WIDTH = 1.0; 
    const float SHADOW_OFFSET = shadowOffset;
    const float SHADOW_OPACITY = 0.8;
    const float SHADOW_RADIUS = 3.0;

    //Our old image now is just background image view with shadow
    UIImageView *backgroundImageView = imageView;
    UIView *superView = backgroundImageView.superview;

    //Make wider actual visible rect taking into account shadow
    //offset
    CGRect oldBackgroundFrame = backgroundImageView.frame;
    CGRect newBackgroundFrame = CGRectMake(oldBackgroundFrame.origin.x, oldBackgroundFrame.origin.y, oldBackgroundFrame.size.width + SHADOW_OFFSET, oldBackgroundFrame.size.height + SHADOW_OFFSET);
    [backgroundImageView removeFromSuperview];
    backgroundImageView.frame = newBackgroundFrame;        

    //Make new UIImageView with rounded corners and put our old image
    CGRect frameForRoundedImageView = CGRectMake(0, 0, oldBackgroundFrame.size.width, oldBackgroundFrame.size.height);
    UIImageView *roundedImageView = [[UIImageView alloc]initWithFrame:frameForRoundedImageView];
    roundedImageView.image = imageView.image;
    [roundedImageView.layer setCornerRadius:CORNER_RADIUS];
    [roundedImageView.layer setBorderColor:[UIColor lightGrayColor].CGColor];        
    [roundedImageView.layer setBorderWidth:BORDER_WIDTH]; 
    [roundedImageView.layer setMasksToBounds:YES];

    //Set shadow preferences
    [backgroundImageView setImage:nil];
    [backgroundImageView.layer setShadowColor:[UIColor blackColor].CGColor];
    [backgroundImageView.layer setShadowOpacity:SHADOW_OPACITY];
    [backgroundImageView.layer setShadowRadius:SHADOW_RADIUS];
    [backgroundImageView.layer setShadowOffset:CGSizeMake(SHADOW_OFFSET, SHADOW_OFFSET)];   

    //Add out two image views back to the view hierarchy.
    [backgroundImageView addSubview:roundedImageView];
    [superView addSubview:backgroundImageView];   
}    
Roman Minenok
  • 9,328
  • 4
  • 26
  • 26
2

Old thread still current...

I've edited Daniel Gindi's method to make it possible to use it with buttons etc. as well. If anyone needs rounded corners or wants to combine round corners and a border it has to be set on the view's layer which is passed to this method. I've also set the rasterization to speed it up a little.

+ (UIView*)putView:(UIView*)view insideShadowWithColor:(CGColorRef)color 
                                 andRadius:(CGFloat)shadowRadius 
                                 andOffset:(CGSize)shadowOffset 
                                 andOpacity:(CGFloat)shadowOpacity
{
    // Must have same position like "view"
    UIView *shadow = [[UIView alloc] initWithFrame:view.frame]; 

    shadow.layer.contentsScale = [UIScreen mainScreen].scale;
    shadow.userInteractionEnabled = YES; // Modify this if needed
    shadow.layer.shadowColor = color;
    shadow.layer.shadowOffset = shadowOffset;
    shadow.layer.shadowRadius = shadowRadius;
    shadow.layer.masksToBounds = NO;
    shadow.clipsToBounds = NO;
    shadow.layer.shadowOpacity = shadowOpacity;
    shadow.layer.rasterizationScale = [UIScreen mainScreen].scale;
    shadow.layer.shouldRasterize = YES;

    [view.superview insertSubview:shadow belowSubview:view];
    [shadow addSubview:view];

    // Move view to the top left corner inside the shadowview 
    // ---> Buttons etc are working again :)
    view.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height);

    return shadow;
}
NickBln
  • 21
  • 2
2

The following worked best for me (this code lies in UIView extension, so self denotes some UIView to which we must add a shadow and round corner)

- (void)addShadowViewWithCornerRadius:(CGFloat)radius {

UIView *container = self.superview;

if (!container) {
    return;
}

UIView *shadowView = [[UIView alloc] init];
shadowView.translatesAutoresizingMaskIntoConstraints = NO;
shadowView.backgroundColor = [UIColor lightGrayColor];
shadowView.layer.cornerRadius = radius;
shadowView.layer.masksToBounds = YES;

[container addSubview:shadowView];
[container bringSubviewToFront:shadowView];

[container addConstraint:[NSLayoutConstraint constraintWithItem:shadowView
                                                      attribute:NSLayoutAttributeWidth
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self
                                                      attribute:NSLayoutAttributeWidth
                                                     multiplier:1.0
                                                       constant:0.0]];
[container addConstraint:[NSLayoutConstraint constraintWithItem:shadowView
                                                      attribute:NSLayoutAttributeLeading
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self
                                                      attribute:NSLayoutAttributeLeading
                                                     multiplier:1.0
                                                       constant:2.0]];

[container addConstraint:[NSLayoutConstraint constraintWithItem:shadowView
                                                      attribute:NSLayoutAttributeHeight
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self
                                                      attribute:NSLayoutAttributeHeight
                                                     multiplier:1.0
                                                       constant:0.0]];
[container addConstraint:[NSLayoutConstraint constraintWithItem:shadowView
                                                      attribute:NSLayoutAttributeTop
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self
                                                      attribute:NSLayoutAttributeTop
                                                     multiplier:1.0
                                                       constant:2.0]];
[container sendSubviewToBack:shadowView];
}

The main difference between this and other code samples is that this adds the shadow view as a sibling view (as against adding the current view as subview of shadow view), thereby eliminating the need to modify the existing view hierarchy in any way.

Mehul Parmar
  • 3,599
  • 3
  • 26
  • 42
2

Swift 4 Solution for making UICollectionViewCell round and adding Shadows, without any extensions and complications :)

Note: For simple views e.g Buttons. See the @suragch's Answer in this post. https://stackoverflow.com/a/34984063/7698092. Tested successfully for buttons

In case if any one still struggling to round the corners and add shadows at the same time. Although this solution works with UICollectionViewCell, it can be generalized to any view.

This technique worked for me without making any extensions and all the complicated stuff. I am working with storyBoard.

Technique

You must add a UIView (lets say it "containerView") inside your UICollectionViewCell in storyBoard and add all the required views (buttons, images etc) inside this containerView. See the Screenshot. Structure of Cell

Connect the outlet for containerView. Add following lines of code in CellforItemAtIndexPath delegate function.

//adds shadow to the layer of cell

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

//makes the cell round 

let containerView = cell.containerView!
    containerView.layer.cornerRadius = 8
    containerView.clipsToBounds = true

Output

See the simulator Screenshot Rounded corners with Shadows (UICollectionViewCell)

Awais Fayyaz
  • 2,275
  • 1
  • 22
  • 45
1

daniel.gindi's answer above did the trick for me! (+1 daniel) However, I had to make minor adjustments - change the shadowFrame size to be same as view's frame size, and enable user interaction. Here's the updated code:

+ (UIView*)putView:(UIView*)view insideShadowWithColor:(UIColor*)color andRadius:(CGFloat)shadowRadius andOffset:(CGSize)shadowOffset andOpacity:(CGFloat)shadowOpacity
{
    CGRect shadowFrame; // Modify this if needed

    // Modified this line
    shadowFrame.size = CGSizeMake(view.frame.size.width, view.frame.size.height);

    shadowFrame.origin.x = 0.f;
    shadowFrame.origin.y = 0.f;
    UIView * shadow = [[UIView alloc] initWithFrame:shadowFrame];

    // Modified this line
    shadow.userInteractionEnabled = YES;
    shadow.layer.shadowColor = color.CGColor;
    shadow.layer.shadowOffset = shadowOffset;
    shadow.layer.shadowRadius = shadowRadius;
    shadow.layer.masksToBounds = NO;
    shadow.clipsToBounds = NO;
    shadow.layer.shadowOpacity = shadowOpacity;

    [shadow addSubview:view];
    return shadow;
}

I would like to add that in my case, I was trying to add this to a 3rd party view controller, i.e. I did not have direct control over the code. So, here's how I used the function above:

UIView *shadow = [self putView:vc.view 
         insideShadowWithColor:[UIColor blackColor]
                     andRadius:5.0 
                     andOffset:CGSizeMake(0.0, 0.0) 
                    andOpacity:1.0];
vc.view = shadow;
vc.view.layer.cornerRadius = 5.0;
vc.view.layer.masksToBounds = YES;
Digitrance
  • 759
  • 7
  • 17
1

I make some changes to the code of daniel.gindi

This is all you need to make it work.

+ (void)putView:(UIView*)view insideShadowWithColor:(UIColor*)color andBlur:         (CGFloat)blur andOffset:(CGSize)shadowOffset andOpacity:(CGFloat)shadowOpacity
{
    CGRect shadowFrame = view.frame;
    UIView * shadow = [[UIView alloc] initWithFrame:shadowFrame];
    shadow.backgroundColor = [UIColor redColor];
    shadow.userInteractionEnabled = YES; // Modify this if needed
    shadow.layer.shadowColor = color.CGColor;
    shadow.layer.shadowOffset = shadowOffset;
    shadow.layer.shadowRadius = blur;
    shadow.layer.cornerRadius = view.layer.cornerRadius;
    shadow.layer.masksToBounds = NO;
    shadow.clipsToBounds = NO;
    shadow.layer.shadowOpacity = shadowOpacity;
    [view.superview insertSubview:shadow belowSubview:view];
}
Carlos López
  • 759
  • 4
  • 13
  • 23
1

You need to use two UIViews to achieve this. One UIView will work like shadow and other one will work for rounded border.

Here is a code snippet a Class Method with a help of a protocol:

@implementation UIMethods

+ (UIView *)genComposeButton:(UIViewController <UIComposeButtonDelegate> *)observer;
{
    UIView *shadow = [[UIView alloc]init];
    shadow.layer.cornerRadius = 5.0;
    shadow.layer.shadowColor = [[UIColor blackColor] CGColor];
    shadow.layer.shadowOpacity = 1.0;
    shadow.layer.shadowRadius = 10.0;
    shadow.layer.shadowOffset = CGSizeMake(0.0f, -0.5f);

    UIButton *btnCompose = [[UIButton alloc]initWithFrame:CGRectMake(0, 0,60, 60)];
    [btnCompose setUserInteractionEnabled:YES];
    btnCompose.layer.cornerRadius = 30;
    btnCompose.layer.masksToBounds = YES;
    [btnCompose setImage:[UIImage imageNamed:@"60x60"] forState:UIControlStateNormal];
    [btnCompose addTarget:observer action:@selector(btnCompose_click:) forControlEvents:UIControlEventTouchUpInside];
    [shadow addSubview:btnCompose];
    return shadow;
}

In the code above btnCompose_click: will become a @required delegate method which will fire on the button click.

And here I added a button to my UIViewController like this:

UIView *btnCompose = [UIMethods genComposeButton:self];
btnCompose.frame = CGRectMake(self.view.frame.size.width - 75,
                          self.view.frame.size.height - 75,
                          60, 60);
[self.view addSubview:btnCompose];

The result will look like this:

enter image description here

Vaibhav Saran
  • 12,848
  • 3
  • 65
  • 75
1

I have tried so many solutions from this post and ended up with the below solution. This is full proof solution unless you need to drop shadow on a clear color view.

- (void)addShadowWithRadius:(CGFloat)shadowRadius withOpacity:(CGFloat)shadowOpacity withOffset:(CGSize)shadowOffset withColor:(UIColor *)shadowColor withCornerradius:(CGFloat)cornerRadius
{
    UIView *viewShadow = [[UIView alloc]initWithFrame:self.frame];
    viewShadow.backgroundColor = [UIColor whiteColor];
    viewShadow.layer.shadowColor = shadowColor.CGColor;
    viewShadow.layer.shadowOffset = shadowOffset;
    viewShadow.layer.shadowRadius = shadowRadius;
    viewShadow.layer.shadowOpacity = shadowOpacity;
    viewShadow.layer.cornerRadius = cornerRadius;
    viewShadow.layer.masksToBounds = NO;
    [self.superview insertSubview:viewShadow belowSubview:self];

    [viewShadow setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:viewShadow attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:viewShadow attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:viewShadow attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:viewShadow attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:viewShadow attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:viewShadow attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];
    [self layoutIfNeeded];

    self.layer.cornerRadius = cornerRadius;
    self.layer.masksToBounds = YES;
}
Mahesh Agrawal
  • 3,348
  • 20
  • 34
1
import UIKit

extension UIView {

    func addShadow(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) {

        let shadowLayer = CAShapeLayer()
        let size = CGSize(width: cornerRadius, height: cornerRadius)
        let cgPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size).cgPath //1
        shadowLayer.path = cgPath //2
        shadowLayer.fillColor = fillColor.cgColor //3
        shadowLayer.shadowColor = shadowColor.cgColor //4
        shadowLayer.shadowPath = cgPath
        shadowLayer.shadowOffset = offSet //5
        shadowLayer.shadowOpacity = opacity
        shadowLayer.shadowRadius = shadowRadius
        self.layer.addSublayer(shadowLayer)
    }
}
Sai kumar Reddy
  • 1,751
  • 20
  • 23
0

The answer provided by Evan Mulawski will work perfectly. The catch is that you have to set the background color for the view to clearColor and the masksToBounds property to NO.

You can set whatever color you want for the view, set it like

v.layer.backgroundColor = your color;

Hope this helps..

0

This is how you do it, with rounded corners and rounded shadows without bothering with paths.

//Inner view with content
[imageView.layer setBorderColor:[[UIColor lightGrayColor] CGColor]];
[imageView.layer setBorderWidth:1.0f];
[imageView.layer setCornerRadius:8.0f];
[imageView.layer setMasksToBounds:YES];

//Outer view with shadow
UIView* shadowContainer = [[UIView alloc] initWithFrame:imageView.frame];
[shadowContainer.layer setMasksToBounds:NO];
[shadowContainer.layer setShadowColor:[[UIColor blackColor] CGColor]];
[shadowContainer.layer setShadowOpacity:0.6f];
[shadowContainer.layer setShadowRadius:2.0f];
[shadowContainer.layer setShadowOffset: CGSizeMake(0.0f, 2.0f)];

[shadowContainer addSubview:imageView];

The view with content, in my case a UIImageView, has a corner radius and therefore has to mask to bounds.

We create another equally sized view for the shadows, set it's maskToBounds to NO and then add the content view to the container view (e.g. shadowContainer).

Ford Davis
  • 343
  • 4
  • 13
0

I write this UIView category method to solve this problem, uses separate views for the shadow and the corner radius.

-(UIView *)shadowedWrapViewWithBounds:(CGRect)bounds {
UIView *baseView = [[UIView alloc] init];
baseView.bounds = bounds;
baseView.backgroundColor = [UIColor clearColor];
baseView.layer.shadowColor = [UIColor blackColor].CGColor;
baseView.layer.shadowOffset = CGSizeMake(0, 0);
baseView.layer.shadowOpacity = 0.7;
baseView.layer.shadowRadius = 4.0;

// improve performance
baseView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:baseView.bounds cornerRadius:4].CGPath;
baseView.layer.shouldRasterize = YES;
baseView.layer.rasterizationScale = [UIScreen mainScreen].scale;

[baseView addSubview:self];
//use Masonry autolayout, self can set corner radius
[self makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(baseView);
}];

return baseView;
}
Liu Chao
  • 51
  • 4
0
extension UIView {
    func dropRoundedShadowForAllSides() {
        let backgroundView = UIView(frame:self.frame)
        let radius = frame.height/2
        backgroundView.layer.masksToBounds = false
        self.layer.masksToBounds = true
        backgroundView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
        backgroundView.layer.shadowRadius = 4
        backgroundView.layer.shadowOpacity = 0.4

        let path = UIBezierPath()

        // Start at the Top Left Corner + radius distance
        path.move(to: CGPoint(x: 2*radius, y: 0.0))

        // Move to the Top Right Corner - radius distance
        path.addLine(to: CGPoint(x: backgroundView.frame.size.width - radius, y: 0.0))

        // Move to top right corner + radius down as curve
        let centerPoint1 = CGPoint(x:backgroundView.frame.size.width - radius,y:radius)
        path.addArc(withCenter: centerPoint1, radius: radius, startAngle: 3*(.pi/2), endAngle: 0, clockwise: true)

        // Move to the Bottom Right Corner - radius
        path.addLine(to: CGPoint(x: backgroundView.frame.size.width, y: backgroundView.frame.size.height - radius))

        // Move to top right corner + radius left as curve
        let centerPoint2 = CGPoint(x:backgroundView.frame.size.width - radius,y:backgroundView.frame.size.height - radius)
        path.addArc(withCenter: centerPoint2, radius: radius, startAngle: 0, endAngle: .pi/2, clockwise: true)

        // Move to the Bottom Left Corner - radius
        path.addLine(to: CGPoint(x: radius, y: backgroundView.frame.size.height))

        // Move to left right corner - radius up as curve
        let centerPoint3 = CGPoint(x:radius,y:backgroundView.frame.size.height - radius)
        path.addArc(withCenter: centerPoint3, radius: radius, startAngle: .pi/2, endAngle: .pi, clockwise: true)

        // Move to the top Left Corner - radius
        path.addLine(to: CGPoint(x: 0, y: radius))

        // Move to top right corner + radius down as curve
        let centerPoint4 = CGPoint(x:radius,y:radius)
        path.addArc(withCenter: centerPoint4, radius: radius, startAngle: .pi, endAngle: 3 * (.pi/2), clockwise: true)

        path.close()

        backgroundView.layer.shadowPath = path.cgPath
        if let superView = self.superview {
            superView.addSubview(backgroundView)
            superView.sendSubview(toBack: backgroundView)
            superView.bringSubview(toFront: self)
        }

    }
}
0

If you specifically want custom rounded corners for UIButtons, there are many different ways of achieving that.

The following code examples (thanks to Erica) give a nice overview over all the possibilities.

Rounded corners button before iOS 15

Before iOS 15, you can make a rounded corners button by setting layer.cornerRadius, backgroundColor, and setTitleColor.

let button = UIButton(type: .system)
button.setTitle("Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemPink
button.layer.cornerRadius = 8

button.contentEdgeInsets = UIEdgeInsets(
  top: 10,
  left: 20,
  bottom: 10,
  right: 20
)

Capsule button

If we increase the corner radius with a large enough value, you can create a capsule-style button. To create a capsule style button, we set corner radius equals the half of a button height. Since a button height might vary based on title size or layout, I usually create a UIButton subclass for a capsule style.

class CapsuleButton: UIButton {
  override func layoutSubviews() {
    super.layoutSubviews()
    let height = bounds.height
    layer.cornerRadius = height/2
  }
}

let capsule = CapsuleButton(type: .system)
capsule.setTitle("Button", for: .normal)
capsule.setTitleColor(.white, for: .normal)
capsule.backgroundColor = .systemPink
capsule.contentEdgeInsets = UIEdgeInsets(
  top: 10,
  left: 20,
  bottom: 10,
  right: 20
)

Smooth corners

If you want to replicate Apple smooth corners, you can also do that with the cornerCurve property. To create a continuous smooth corner, you set layer.cornerCurve of a button to .continuous.

let button = UIButton(type: .system)
button.setTitle("Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemPink
button.layer.cornerRadius = 8
button.layer.cornerCurve = .continuous

button.contentEdgeInsets = UIEdgeInsets(
  top: 10,
  left: 20,
  bottom: 10,
  right: 20
)

Rounded corners button in iOS 15 using UIButton.Configuration

In iOS 15, Apple introduces a new way to customize a button via UIButton.Configuration.

var configuration = UIButton.Configuration.filled()

configuration.title = "Button"
configuration.baseBackgroundColor = UIColor.systemPink
configuration.contentInsets = NSDirectionalEdgeInsets(
  top: 10,
  leading: 20,
  bottom: 10,
  trailing: 20
)

let button = UIButton(configuration: configuration, primaryAction: nil)

Control corner radius using cornerStyle

UIButton.Configuration has many built-in corner styles for you to choose from. You can set this with the cornerStyle property.

configuration.cornerStyle = .small
configuration.cornerStyle = .medium
configuration.cornerStyle = .large
configuration.cornerStyle = .capsule

Custom corner radius

If you want something more unique, you can specify the corner radius via cornerRadius of UIBackgroundConfiguration.

var configuration = UIButton.Configuration.filled()
configuration.title = "Button"
configuration.baseBackgroundColor = UIColor.systemPink
configuration.contentInsets = NSDirectionalEdgeInsets(
  top: 10,
  leading: 20,
  bottom: 10,
  trailing: 20
)
configuration.background.cornerRadius = 20

configuration.cornerStyle = .fixed


let button = UIButton(configuration: configuration, primaryAction: nil)
blackjacx
  • 9,011
  • 7
  • 45
  • 56
-1

You need add masksToBounds = true for combined between corderRadius shadowRadius.

button.layer.masksToBounds = false;
Phillip
  • 4,276
  • 7
  • 42
  • 74
-3
var shadows = UIView()
shadows.frame = view.frame
shadows.clipsToBounds = false
view.addSubview(shadows)


let shadowPath0 = UIBezierPath(roundedRect: shadows.bounds, cornerRadius: 10)
let layer0 = CALayer()
layer0.shadowPath = shadowPath0.cgPath
layer0.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.23).cgColor
layer0.shadowOpacity = 1
layer0.shadowRadius = 6
layer0.shadowOffset = CGSize(width: 0, height: 3)
layer0.bounds = shadows.bounds
layer0.position = shadows.center

shadows.layer.addSublayer(layer0)

David Buck
  • 3,752
  • 35
  • 31
  • 35