Shadow not showing when background color is clear color - ios

I've created an uiview in my xib with background color as clear color. When I apply the shadow on the layer of the view, the shadow is not appearing. But when i set the background color other than clear color, shadow is showing. Please help.
this is my code
self.cView.layer.shadowColor=[UIColor whiteColor].CGColor;
self.cView.layer.shadowOffset=CGSizeZero;
self.cView.layer.shadowRadius=30.0;
self.cView.layer.shadowOpacity=1.0;
self.cView.layer.cornerRadius=10.0;

The problem is, that shadow actually takes into account the 'upper' layer. If there's nothing on it there will be no shadow: How Shadows Work
EDIT:
There is this recipe copied from paste bin
view.layer.shadowColor = [UIColor colorWithWhite:.5 alpha:1].CGColor;
view.layer.shadowRadius = 4.0f;
view.layer.shadowPath = CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL);
view.layer.shadowOpacity = 1.0f;
view.layer.shadowOffset = CGSizeMake(1, 1);
But I doubt this will be of any use to you: the result is a view 'painted' with color of a shadow and a shadow around it.

If you specify shadowPath property
shadowView.layer.shadowPath =
UIBezierPath(
roundedRect: shadowView.bounds,
cornerRadius: 10).cgPath
(Or whatever corner radius is desired.)
it will work even with .clear backgroundColor.
Note that you of course have to do this in layoutSubviews of the view in question.
Here's an actual full working example:
import UIKit
#IBDesignable class LonelyShadow: UIView {
let corner: CGFloat = 20
override init(frame: CGRect) {
super.init(frame: frame)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
private func common() {
backgroundColor = .clear
clipsToBounds = false
layer.shadowColor = UIColor.yourColor.cgColor
layer.shadowOffset = CGSize(width: 0, height: 25)
layer.shadowOpacity = 0.3
layer.shadowRadius = 40
}
override func layoutSubviews() {
super.layoutSubviews()
layer.shadowPath = UIBezierPath(
roundedRect: bounds, cornerRadius: corner).cgPath
}
}

Equivalent to #Rok Jark's answer in Swift 4:
self.layer.shadowColor = UIColor(white: 0.5, alpha: 1).cgColor
self.layer.shadowRadius = 4.0
self.layer.shadowPath = CGPath.init(rect: CGRect.init(x: 0, y: 0, width: 50, height: 50), transform: nil)
self.layer.shadowOpacity = 1.0;
self.layer.shadowOffset = CGSize(width: 1, height: 1)

Related

dashed border not working for UITableview cell's child view(dashed border width and hight not same as the view)

Here you can see the view which is inside the TableViewell:
Here is the code for adding dashed border:
func addDashedBorder()
{
let color = UIColor(displayP3Red: 178.0/255.0, green: 181.0/255.0, blue: 200.0/255.0, alpha: 1.0).cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let shapeRect = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPattern = [6,3]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 4).cgPath
self.layer.addSublayer(shapeLayer)
}
I'm calling this function inside the awake from nib like this:
view.addDashedBorder().
Much easier approach...
Subclass UIView and put the border-related code inside it:
class DashedView: UIView {
let shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
let color = UIColor(displayP3Red: 178.0/255.0, green: 181.0/255.0, blue: 200.0/255.0, alpha: 1.0).cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPattern = [6,3]
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 4).cgPath
}
}
Now you don't need any sort of view.addDashedBorder() calls.
Reason for issue: You are calling your border adding code from awakeFromNib.
Awake from is called when nib loaded, so that time your cell will not have the exact size for all subviews, when awakeFromNib gets called, all views have size as same as nib design. so awake from nib is the wrong place when you are setting or updating something which is related to size of any view.
Solution: The right place to do this is func layoutSubviews()
layoutSubviews() gets called when the size of any subview will be changed or the frames of views/subviews gets updated first time.
This is the right place to add border or your view, and make sure it will only one call of adding border because layoutSubviews will get multiple calls if the frame of any subview will change.**
Reference:
https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews

Custom #IBDesignable UIButton crashes Xcode

I wanted to design a rounded button with a shadow for my iOS project in Swift. So I came up with the following custom button class:
import UIKit
#IBDesignable class MainButton: UIButton {
private var shadowLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
let image = createBackgroundImage()
setBackgroundImage(image, for: UIControl.State.normal)
clipsToBounds = true
contentEdgeInsets = UIEdgeInsets(top: 5, left: 20, bottom: 5, right: 20)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowOpacity = 0.2
layer.shadowRadius = 6
}
func createBackgroundImage() -> UIImage {
let rect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
let color = UIColor.white
color.setFill()
UIBezierPath(roundedRect: rect, cornerRadius: frame.height * 0.5).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
As soon as I set this class for a button in my Storyboard the "IBDEsignablesAgent-iOS" process takes nearly 100% of my CPU and Xcode hangs it self. This behavior makes it near impossible to debug the problem correctly.
I'm quite certain I do something in the wrong order or wrong method. But I have no clue how to solve it. Hopefully someone here can point me the right direction.
Thanks,
Jens
Simply check that the size of the view actually changed:
private var lastSize: CGSize = .zero
override func layoutSubviews() {
super.layoutSubviews()
guard frame.size != lastSize else { return }
lastSize = frame.size
...
}
Creating shadows is slow and layoutSubviews is called tens of times every second (basically, once every frame).
My custom button with some shadow and rounded corners, I use it directly within the Storyboard, no need to touch it programmatically.
class RoundedCornerButtonWithShadow: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.layer.masksToBounds = false
self.layer.cornerRadius = self.frame.height/2
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
}
}

Adding Corner radius for selected edges with shadow using UIBezierPath

I am trying to add a shadow on my view. It is having 3 rounded edges. To achieve this UIBezierPath and then setting the CAShapelayer with this path as the mask on the layer of the view. Now, if I'm trying to add shadow on this view, it is not showing. I have gone through a similar question and suggested answers but nothing works in my case. Following is my implementation:
class BubbleView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
}
override func layoutSubviews() {
super.layoutSubviews()
self.updateContainerLayer()
}
func updateContainerLayer() {
let brazierPath: UIBezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
//1
let shapeLayer = CAShapeLayer()
shapeLayer.path = brazierPath.cgPath
self.layer.mask = shapeLayer
//2
self.layer.shadowColor = UIColor(r: 0, g: 0, b: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
self.layer.shadowRadius = 1.5
self.layer.shadowPath = brazierPath.cgPath
//3
self.layer.masksToBounds = true
self.clipsToBounds = false
//4
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
If you are targeting iOS 11+ you can use the layer's .maskedCorners property:
class BubbleView: UIView {
// don't override draw()
// override func draw(_ rect: CGRect) {
// super.draw(rect)
// }
override func layoutSubviews() {
super.layoutSubviews()
self.updateContainerLayer()
}
func updateContainerLayer() {
let brazierPath: UIBezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
//1
// let shapeLayer = CAShapeLayer()
// shapeLayer.path = brazierPath.cgPath
// self.layer.mask = shapeLayer
// iOS 11+ use .maskedCorners
self.layer.cornerRadius = 15.0
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
//2
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
self.layer.shadowRadius = 1.5
self.layer.shadowPath = brazierPath.cgPath
//3
self.layer.masksToBounds = true
self.clipsToBounds = false
//4
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
Result:
Result with an exaggerated .shadowOffset = CGSize(width: -10.0, height: 10.5) to make it easy to see the shadow:
If you need to allow earlier iOS versions, I believe you'll need to use a container view approach.
EDIT:
Another approach, using a "container" view for the shadow. This will work with iOS earlier than 11... it uses the same UIBezierPath for the "content view" mask and the shadow path:
class BubbleView: UIView {
let contentView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
self.clipsToBounds = false
backgroundColor = .clear
contentView.backgroundColor = .red
// set non-changing properties here
contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
// exaggerated shadow offset so we can see it easily
//self.layer.shadowOffset = CGSize(width: -10.0, height: 10.5)
self.layer.shadowRadius = 1.5
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
override func layoutSubviews() {
super.layoutSubviews()
let bezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
contentView.layer.mask = shapeLayer
self.layer.shadowPath = bezierPath.cgPath
}
}
As with the 11+ example, Result:
and Result with an exaggerated .shadowOffset = CGSize(width: -10.0, height: 10.5) to make it easy to see the shadow:

UIImage Inside Custom Shape and animation

I need to draw a shape like that one:
And animate the color path when you are scrolling. I have been for 2 hours trying several things but don't reach the final way to overcome that. I have tried creating a custom UIView, create a CShapeLayer and then a UIBezierPath to that layer, and finally adding the subview to a view in the viewdidload, but this does not work. What else can I do to approach that? I will start with the shapes that are the most complex things, as the label will be just aligned to the shape.
----FIRST PART SOLVED, DRAWRECT NOW APPEARS, BUT HOW DO I ANIMATE?----
That's my updated code in my drawRect method.
class CustomOval: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func drawRect(rect: CGRect) {
//SHAPE 2
let rectanglePath2 = UIBezierPath(roundedRect: CGRectMake(135, 177, 20, 70), cornerRadius: 0)
let shapeLayer2 = CAShapeLayer()
shapeLayer2.path = rectanglePath2.CGPath
shapeLayer2.bounds = CGRect(x: 135, y: 177, width: 20, height: 70)
shapeLayer2.lineWidth = 5.0
let label2 = UILabel()
label2.frame = CGRectMake(150 + rectanglePath2.bounds.width, rectanglePath2.bounds.height + 120, 100, 50)
label2.text = "Label 2"
self.addSubview(label2)
//// Rectangle Drawing
UIColor.blueColor().setFill()
rectanglePath2.fill()
//SHAPE 3
let rectanglePath3 = UIBezierPath(roundedRect: CGRectMake(135, 237, 20, 70), cornerRadius: 0)
let shapeLayer3 = CAShapeLayer()
shapeLayer3.path = rectanglePath3.CGPath
shapeLayer3.bounds = CGRect(x: 135, y: rectanglePath2.bounds.maxY, width: 20, height: 70)
shapeLayer3.lineWidth = 5.0
//// Rectangle Drawing
UIColor.redColor().setFill()
rectanglePath3.fill()
let label3 = UILabel()
label3.frame = CGRectMake(rectanglePath3.bounds.width + 150, rectanglePath3.bounds.height + 190, 100, 50)
label3.text = "Label 3"
self.addSubview(label3)
//SHAPE 1
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(104, 24, 80, 155), cornerRadius: 40)
let shapeLayer = CAShapeLayer()
shapeLayer.path = rectanglePath.CGPath
shapeLayer.bounds = CGRect(x: 104, y: 24, width: 80, height: 155)
shapeLayer.lineWidth = 5.0
//// Rectangle Drawing
UIColor.grayColor().setFill()
rectanglePath.fill()
}
}
-UPDATE ANIMATION-
var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
shapeLayer = CAShapeLayer()
super.init(frame: frame)
animateShape1()
}
required init(coder aDecoder: NSCoder) {
shapeLayer = CAShapeLayer()
super.init(coder: aDecoder)!
animateShape1()
}
func animateShape1(){
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 5 //2 sec
shapeLayer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")
}
My main questions now are:
How do I set an image inside the CAShapeLayer? I have tried with: FIXED
Calling it from the init
func addImage(){
let imageSubLayer = CALayer()
let image = UIImage(named: "paint.png")
imageSubLayer.contents = image?.CGImage
imageSubLayer.bounds = (frame: CGRect(x: shapeLayer.bounds.width/2, y: shapeLayer.bounds.height/2, width: 50, height: 50))
shapeLayer.addSublayer(imageSubLayer)
}
I have also tried, and the one that has worked is: But i dont want a tiled image. I have also tried without tiled and this does not work
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextScaleCTM(context, 0, (image?.size.height)!)
CGContextDrawTiledImage(context, CGRectMake(20, 20, 50, 50), image!.CGImage)
CGContextRestoreGState(context)
I also need to animate the process, and I have tried with the reply
of #s0urce but unfortunately, it is not drawing. Do I need to do
something else?
You should never add sublayers inside drawRect method. It would be much better to do this inside init or initWithFrame: method of a view or at least inside viewDidLoad method of view controller.
You need to set bounds of CAShapeLayer otherwise it would be CGRectZero. Don't forget that points of UIBezierPath should be in the coordinate system of CAShapeLayer.
Color animation sample code:
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 2 //2 sec
layer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")

Swift - Problems with corner radius and drop shadow

I'm trying to create a button with rounded corners and a drop shadow. No matter how I switch up, the button will not display correctly. I've tried masksToBounds = false and masksToBounds = true, but either the corner radius works and the shadow does not or the shadow works and the corner radius doesn't clip the corners of the button.
import UIKit
import QuartzCore
#IBDesignable
class Button : UIButton
{
#IBInspectable var masksToBounds: Bool = false {didSet{updateLayerProperties()}}
#IBInspectable var cornerRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var borderWidth : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var borderColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
#IBInspectable var shadowColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
#IBInspectable var shadowOpacity: CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var shadowRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
#IBInspectable var shadowOffset : CGSize = CGSizeMake(0, 0) {didSet{updateLayerProperties()}}
override func drawRect(rect: CGRect)
{
updateLayerProperties()
}
func updateLayerProperties()
{
self.layer.masksToBounds = masksToBounds
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.CGColor
self.layer.shadowColor = shadowColor.CGColor
self.layer.shadowOpacity = CFloat(shadowOpacity)
self.layer.shadowRadius = shadowRadius
self.layer.shadowOffset = shadowOffset
}
}
The following Swift 5 / iOS 12 code shows how to set a subclass of UIButton that allows to create instances with rounded corners and shadow around it:
import UIKit
final class CustomButton: UIButton {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
//layer.insertSublayer(shadowLayer, below: nil) // also works
}
}
}
According to your needs, you may add a UIButton in your Storyboard and set its class to CustomButton or you may create an instance of CustomButton programmatically. The following UIViewController implementation shows how to create and use a CustomButton instance programmatically:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = CustomButton(type: .system)
button.setTitle("Button", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraint = button.widthAnchor.constraint(equalToConstant: 100)
let heightConstraint = button.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
}
The previous code produces the image below in the iPhone simulator:
My custom button with some shadow and rounded corners, I use it directly within the Storyboard with no need to touch it programmatically.
Swift 4
class RoundedButtonWithShadow: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.layer.masksToBounds = false
self.layer.cornerRadius = self.frame.height/2
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
}
}
To expand on Imanou's post, it's possible to programmatically add the shadow layer in the custom button class
#IBDesignable class CustomButton: UIButton {
var shadowAdded: Bool = false
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
if shadowAdded { return }
shadowAdded = true
let shadowLayer = UIView(frame: self.frame)
shadowLayer.backgroundColor = UIColor.clearColor()
shadowLayer.layer.shadowColor = UIColor.darkGrayColor().CGColor
shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: self.cornerRadius).CGPath
shadowLayer.layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
shadowLayer.layer.shadowOpacity = 0.5
shadowLayer.layer.shadowRadius = 1
shadowLayer.layer.masksToBounds = true
shadowLayer.clipsToBounds = false
self.superview?.addSubview(shadowLayer)
self.superview?.bringSubviewToFront(self)
}
}
An alternative way to get more usable and consistent button.
Swift 2:
func getImageWithColor(color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage {
let rect = CGRectMake(0, 0, size.width, size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 1)
UIBezierPath(
roundedRect: rect,
cornerRadius: cornerRadius
).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
let button = UIButton(type: .Custom)
button.frame = CGRectMake(20, 20, 200, 50)
button.setTitle("My Button", forState: UIControlState.Normal)
button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
self.addSubview(button)
let image = getImageWithColor(UIColor.whiteColor(), size: button.frame.size, cornerRadius: 5)
button.setBackgroundImage(image, forState: UIControlState.Normal)
button.layer.shadowRadius = 5
button.layer.shadowColor = UIColor.blackColor().CGColor
button.layer.shadowOpacity = 0.5
button.layer.shadowOffset = CGSizeMake(0, 1)
button.layer.masksToBounds = false
Swift 3:
func getImageWithColor(_ color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage? {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
let button = UIButton(type: .custom)
button.frame = CGRect(x:20, y:20, width:200, height:50)
button.setTitle("My Button", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
self.addSubview(button)
if let image = getImageWithColor(UIColor.white, size: button.frame.size, cornerRadius: 5) {
button.setBackgroundImage(image, for: .normal)
}
button.layer.shadowRadius = 5
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowOpacity = 0.5
button.layer.shadowOffset = CGSize(width:0, height:1)
button.layer.masksToBounds = false
Swift 5 &
No need of "UIBezierPath"
view.layer.cornerRadius = 15
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowRadius = 7
view.layer.shadowOpacity = 0.6
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowColor = UIColor.red.cgColor
Refactored this to support any view. Subclass your view from this and it should have rounded corners. If you add something like a UIVisualEffectView as a subview to this view you likely need to use the same rounded corners on that UIVisualEffectView or it won't have rounded corners.
/// Inspiration: https://stackoverflow.com/a/25475536/129202
class ViewWithRoundedcornersAndShadow: UIView {
private var theShadowLayer: CAShapeLayer?
override func layoutSubviews() {
super.layoutSubviews()
if self.theShadowLayer == nil {
let rounding = CGFloat.init(22.0)
let shadowLayer = CAShapeLayer.init()
self.theShadowLayer = shadowLayer
shadowLayer.path = UIBezierPath.init(roundedRect: bounds, cornerRadius: rounding).cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowRadius = CGFloat.init(3.0)
shadowLayer.shadowOpacity = Float.init(0.2)
shadowLayer.shadowOffset = CGSize.init(width: 0.0, height: 4.0)
self.layer.insertSublayer(shadowLayer, at: 0)
}
}
}
Exact solution for 2020 syntax
import UIKit
class ColorAndShadowButton: UIButton {
override init(frame: CGRect) { super.init(frame: frame), common() }
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder), common() }
private func common() {
// UIButton is tricky: you MUST set the clear bg in bringup; NOT in layout
backgroundColor = .clear
clipsToBounds = false
layer.insertSublayer(colorAndShadow, below: layer)
}
lazy var colorAndShadow: CAShapeLayer = {
let s = CAShapeLayer()
// set your button color HERE (NOT on storyboard)
s.fillColor = UIColor.black.cgColor
// now set your shadow color/values
s.shadowColor = UIColor.red.cgColor
s.shadowOffset = CGSize(width: 0, height: 10)
s.shadowOpacity = 1
s.shadowRadius = 10
// now add the shadow
layer.insertSublayer(s, at: 0)
return s
}()
override func layoutSubviews() {
super.layoutSubviews()
// you MUST layout these two EVERY layout cycle:
colorAndShadow.frame = bounds
colorAndShadow.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
}
}
Note that the very old top answer here is correct but has a critical error
Note that UIButton is unfortunately quite different from UIView in iOS.
Due to a strange behavior in iOS, you must set the background color (which of course must be clear in this case) in initialization, not in layout. You could just set it clear in storyboard (but you usually click it to be some solid color simply so you can see it when working in storyboard.)
In general combos of shadows/rounding are a real pain in iOS. Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers
To improve PiterPan's answer and show a real shadow (not just a background with no blur) with a circular button in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
myButton.layer.masksToBounds = false
myButton.layer.cornerRadius = myButton.frame.height/2
myButton.clipsToBounds = true
}
override func viewDidLayoutSubviews() {
addShadowForRoundedButton(view: self.view, button: myButton, opacity: 0.5)
}
func addShadowForRoundedButton(view: UIView, button: UIButton, opacity: Float = 1) {
let shadowView = UIView()
shadowView.backgroundColor = UIColor.black
shadowView.layer.opacity = opacity
shadowView.layer.shadowRadius = 5
shadowView.layer.shadowOpacity = 0.35
shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
shadowView.layer.cornerRadius = button.bounds.size.width / 2
shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: button.frame.origin.y), size: CGSize(width: button.bounds.width, height: button.bounds.height))
self.view.addSubview(shadowView)
view.bringSubview(toFront: button)
}
Corner Radius with Shadow
Short and simple way !!!!!
extension CALayer {
func applyCornerRadiusShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0,
cornerRadiusValue: CGFloat = 0)
{
cornerRadius = cornerRadiusValue
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
Use of code
btn.layer.applyCornerRadiusShadow(color: .black,
alpha: 0.38,
x: 0, y: 3,
blur: 10,
spread: 0,
cornerRadiusValue: 24)
No need maskToBound
Please verify clipsToBounds is false.
OUTPUT
Extension to drop shadow and corner radius
extension UIView {
func dropShadow(color: UIColor, opacity: Float = 0.5, offSet: CGSize, shadowRadius: CGFloat = 1, scale: Bool = true, cornerRadius: CGFloat) {
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = offSet
shadowLayer.shadowOpacity = opacity
shadowLayer.shadowRadius = shadowRadius
layer.insertSublayer(shadowLayer, at: 0)
}
}
Here is the solution that will work!
extension UIView {
func applyShadowWithCornerRadius(color:UIColor, opacity:Float, radius: CGFloat, edge:AIEdge, shadowSpace: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 = self.frame.size.height / 2
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
}
}
enum AIEdge:Int {
case
Top,
Left,
Bottom,
Right,
Top_Left,
Top_Right,
Bottom_Left,
Bottom_Right,
All,
None
}
Finally, to apply shadow with corner radius call as per below:
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
Result Image
UPDATE: If you don't see the expected output then try calling the extension method from Main Thread, that will work for sure!
DispatchQueue.main.async {
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
}
UIButton Extension
Many people have proposed using a custom class of UIButton which is totally fine. Just in case you want an extension, like me, here's one. Written in Swift 5.
extension UIButton {
/// Adds a shadow to the button, with a corner radius
/// - Parameters:
/// - corner: The corner radius to apply to the shadow and button
/// - color: The color of the shaodw
/// - opacity: The opacity of the shadow
/// - offset: The offset of the shadow
/// - radius: The radius of the shadow
func addShadow(corner: CGFloat = 20, color: UIColor = .black, opacity: Float = 0.3, offset: CGSize = CGSize(width: 0, height: 5), radius: CGFloat = 5) {
let shadowLayer = CAShapeLayer()
layer.cornerRadius = corner
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: corner).cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = offset
shadowLayer.shadowOpacity = opacity
shadowLayer.shadowRadius = radius
layer.insertSublayer(shadowLayer, at: 0)
}
}
If somebody need add shadows to rounded buttons in Swift 3.0, here is a good method to do it.
func addShadowForRoundedButton(view: UIView, button: UIButton, shadowColor: UIColor, shadowOffset: CGSize, opacity: Float = 1) {
let shadowView = UIView()
shadowView.backgroundColor = shadowColor
shadowView.layer.opacity = opacity
shadowView.layer.cornerRadius = button.bounds.size.width / 2
shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x + shadowOffset.width, y: button.frame.origin.y + shadowOffset.height), size: CGSize(width: button.bouds.width, height: button.bounds.height))
self.view.addSubview(shadowView)
view.bringSubview(toFront: button)
}
Use this method in func viewDidLayoutSubviews() as bellow:
override func viewDidLayoutSubviews() {
addShadowForRoundedButton(view: self.view, button: button, shadowColor: .black, shadowOffset: CGSize(width: 2, height: 2), opacity: 0.5)
}
The effect of this method is:
You can create a protocol and conform it to you UIView, UIButton, Cell or whatever you want like that:
protocol RoundedShadowable: class {
var shadowLayer: CAShapeLayer? { get set }
var layer: CALayer { get }
var bounds: CGRect { get }
}
​
extension RoundedShadowable {
func applyShadowOnce(withCornerRadius cornerRadius: CGFloat, andFillColor fillColor: UIColor) {
if self.shadowLayer == nil {
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = fillColor.cgColor
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
shadowLayer.shadowOpacity = 0.2
shadowLayer.shadowRadius = 3
self.layer.insertSublayer(shadowLayer, at: 0)
self.shadowLayer = shadowLayer
}
}
}
​
class RoundShadowView: UIView, RoundedShadowable {
var shadowLayer: CAShapeLayer?
private let cornerRadius: CGFloat
private let fillColor: UIColor
init(cornerRadius: CGFloat, fillColor: UIColor) {
self.cornerRadius = cornerRadius
self.fillColor = fillColor
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
}
}
​
class RoundShadowButton: UIButton, RoundedShadowable {
var shadowLayer: CAShapeLayer?
private let cornerRadius: CGFloat
private let fillColor: UIColor
init(cornerRadius: CGFloat, fillColor: UIColor) {
self.cornerRadius = cornerRadius
self.fillColor = fillColor
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor)
}
}

Resources