Aditya Vaidyam
Aditya Vaidyam

Reputation: 6267

UIView with rounded corners and drop shadow?

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


Editor's note: Note that this extremely old question has been wholly outdated (for years) by changes in iOS.

Upvotes: 450

Views: 338359

Answers (30)

Fattie
Fattie

Reputation: 12272

2024 NOW DEAD-EASY, .maskedCorners:

Per user @HangarRash, has been available since iOS11:

class LiveNews: UIIView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // Do this:
        clipsToBounds = false
        
        // The corners:
        backgroundColor = .systemBlue
        layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner,
                                 .layerMaxXMaxYCorner, .layerMaxXMinYCorner]
        layer.cornerRadius = 20
        
        // The shadow:
        layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath
        layer.shadowColor = UIColor.systemYellow.cgColor
        layer.shadowRadius = 1
        layer.shadowOpacity = 1
        layer.shadowOffset = CGSize(width: -4, height: 4)
    }
}

IT IS THAT SIMPLE:

You can set the corners, and the shadow, totally independently. Done.

enter image description here

enter image description here

enter image description here

Critical "slightly expand the shadow" tip:

You almost always want:

.shadowPath = UIBezierPath(roundedRect: bounds.insetBy(dx: -4, dy: -4), etc

to expand the size of the shadow a little.

Footnote:

Obviously, as an iOS engineering matter, you need put only the .shadowPath actually in layoutSubviews. Most teams put the other initialization of a view in some "common init" call. To save you typing ...

class SweetTopRoundedPanel: UIIView {
    
    override func yourInitCall() {
        super.yourInitCall()
        
        clipsToBounds = false
        
        backgroundColor = .secondarySystemBackground
        layer.cornerCurve = .continuous
        layer.cornerRadius = 40
        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        
        layer.borderColor = UIColor.systemPink.cgColor
        layer.borderWidth = 1
        
        layer.shadowColor = UIColor.systemYellow.cgColor
        layer.shadowRadius = 5
        layer.shadowOpacity = 0.7
        layer.shadowOffset = CGSize(width: 0, height: 0)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        layer.shadowPath = UIBezierPath(
            roundedRect: bounds.insetBy(dx: -3, dy: -2),
            cornerRadius: layer.cornerRadius).cgPath
    }
}

Footnote to footnote:

As night follows day, a couple folks DM'd asking how to do the Most teams put the other initialization of a view in some "common init" call part. Others may have something better but for UIView I do this

// file UIIView.swift (note the extra "I" !)

import UIKit

///Note the extra "I". Adds "I"initialization to the UIKit class.
class UIIView: UIView {

    override init(frame: CGRect) {
        
        super.init(frame: frame)
        common()
    }

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

    func common() {

    }
}

Upvotes: 2

Evan Mulawski
Evan Mulawski

Reputation: 55354

Outdated

This answer is no longer applicable to supported versions of iOS.

See https://stackoverflow.com/a/34984063/483349 instead.


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.

Upvotes: 463

yoAlex5
yoAlex5

Reputation: 34401

iOS shadow and cornerRadius

[iOS CALayer]

You are able to setup shadow and/or cornerRadius using layer

//shadow
view1.layer.masksToBounds = false
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.masksToBounds = true
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

Shadow can go out of superview's frame when superview's clipsToBounds is false

cornerRadius plus shadow

  • When you use cornerRadius you set view.layer.masksToBounds = true to clip unnecesssary parts
  • When you use shadow you set view.layer.masksToBounds = false to display the shadow out of view

When you need both you can use different views for cornerRadius and shadow

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
  • CALayer.contents(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 CALayer.cornerRadius, shadow has some performance impact

As for CALayer.cornerRadius perfomance:

Applying it color blending[About] is used

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:

  • 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
}

Upvotes: 4

blackjacx
blackjacx

Reputation: 10510

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)

Upvotes: 0

Dhaval H. Nena
Dhaval H. Nena

Reputation: 4160

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

Upvotes: 6

Kunal Pandey
Kunal Pandey

Reputation: 1

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)

Upvotes: -3

Sergey Grishchev
Sergey Grishchev

Reputation: 12051

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!

Upvotes: 102

Sai kumar Reddy
Sai kumar Reddy

Reputation: 1829

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)
    }
}

Upvotes: 1

jeevan eashwar
jeevan eashwar

Reputation: 17

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)
        }

    }
}

Upvotes: 0

onmyway133
onmyway133

Reputation: 48175

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

Upvotes: 7

rbaldwin
rbaldwin

Reputation: 4888

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

Upvotes: 15

dGambit
dGambit

Reputation: 531

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)

:)

Upvotes: 5

Awais Fayyaz
Awais Fayyaz

Reputation: 2415

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)

Upvotes: 2

Alex Stone
Alex Stone

Reputation: 47354

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]];

Upvotes: 27

Suragch
Suragch

Reputation: 512516

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.

Upvotes: 750

benmore99
benmore99

Reputation: 933

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

Upvotes: 3

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

button.layer.masksToBounds = false;

Upvotes: -1

Thom&#225;s Pereira
Thom&#225;s Pereira

Reputation: 9778

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

Upvotes: 5

Liu Chao
Liu Chao

Reputation: 51

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;
}

Upvotes: 0

Mahesh Agrawal
Mahesh Agrawal

Reputation: 3358

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;
}

Upvotes: 1

Yung Dai
Yung Dai

Reputation: 159

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)

Upvotes: 3

Mehul Parmar
Mehul Parmar

Reputation: 3699

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.

Upvotes: 2

David C.
David C.

Reputation: 825

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.

Upvotes: 80

daniel.gindi
daniel.gindi

Reputation: 3496

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:)

Upvotes: 17

lwz7512
lwz7512

Reputation: 81

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!

Upvotes: 5

Darshit Shah
Darshit Shah

Reputation: 2382

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];

Upvotes: 3

Ford Davis
Ford Davis

Reputation: 343

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).

Upvotes: 0

Vaibhav Saran
Vaibhav Saran

Reputation: 12908

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

Upvotes: 1

Shaopeng Wang
Shaopeng Wang

Reputation: 31

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;

Upvotes: 3

Ade
Ade

Reputation: 631

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 ;

Upvotes: 43

Related Questions