Adding Corner radius for selected edges with shadow using UIBezierPath - ios

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:

Related

Here corner is working but shadow is not work in iOS 9 and 10

Here corner is working but shadow is not work in iOS 9 and 10
and iOS 11 both are working fine
viewBlueMe.chatCellDesign([.topLeft,.bottomLeft,.topRight], [.layerMinXMinYCorner,.layerMinXMaxYCorner,.layerMaxXMinYCorner], radius: 5)
extenstion :
extension UIView {
func chatCellDesign(_ corners:UIRectCorner,_ cormerMask:CACornerMask, radius: CGFloat) {
if #available(iOS 11.0, *){
self.clipsToBounds = false
self.layer.cornerRadius = radius
self.layer.maskedCorners = cormerMask
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowOffset = CGSize.zero
self.layer.shadowRadius = 5
}else{
let rectShape = CAShapeLayer()
rectShape.bounds = self.frame
rectShape.position = self.center
rectShape.path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
self.layer.mask = rectShape
// FIXME: THIS CODE IS NOT WORK
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowOffset = CGSize.zero
self.layer.shadowRadius = 5
}
}
}
i need this type of output in iOS 9 and 10 also this is iOS11
Thanks in advance
Create one outerview and place your UILabel inside it, and please find below details,
outerView.layer.cornerRadius = 5.0
outerView.layer.shadowColor = UIColor.black.cgColor
outerView.layer.shadowOffset = CGSize.zero
outerView.layer.shadowOpacity = 0.2
outerView.layer.shadowRadius = 5.0
UPDATE
See below image,
and this is IBOutlets
#IBOutlet weak var tmpView: UIView!
#IBOutlet weak var innerView: UIView!
You can do this way,
innerView.roundCorners([.topLeft, .topRight, .bottomLeft], radius: 5)
tmpView.layer.shadowColor = UIColor.black.cgColor
tmpView.layer.shadowOffset = CGSize.zero
tmpView.layer.shadowOpacity = 0.2
tmpView.layer.shadowRadius = 10.0
Find UIView extension here,
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
this will give you below output,
FYI. Play around shadow property for exact output you want.

How To Add Border To a UIView With Mask?

Here's what I need to do to my two buttons.
and here's what I got right now.
What I'm doing:
- Set these up in IB inside a stackView.
- Add masks
- Add borders with width and color
- Add shadow.
The borders are being added but being cut-off as well by the masks.
Codes:
public func addCornerRadiusToCorners(
_ corners: UIRectCorner,
cornerRadius: CGFloat,
borderColor: UIColor,
borderWidth: CGFloat) {
// self.layer.masksToBounds = true
// self.clipsToBounds = true
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.cgColor
let size = CGSize(width: cornerRadius, height: cornerRadius)
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size)
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
public func addDefaultShadow() {
let shadowPath = UIBezierPath(rect: self.bounds)
self.layer.masksToBounds = false
self.layer.shadowOffset = CGSize(width: 1, height: 1)
self.layer.shadowOpacity = 0.5
self.layer.shadowPath = shadowPath.cgPath
}
Any idea how to achieve the result in the first photo?
EDIT: the border is being cut, the result was just got from the UI Debugger of Xcode. Sorry.
Add corner radius to you view on viewDidAppear
#IBOutlet weak var segmentView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
segmentView.layer.cornerRadius = segmentView.frame.size.height/2;
segmentView.layer.borderWidth = 1.0;
segmentView.clipsToBounds = true
segmentView.layer.borderColor = UIColor.lightGray.cgColor
segmentView.layer.shadowColor = UIColor.black.cgColor
segmentView.layer.shadowOpacity = 0.2
segmentView.layer.shadowRadius = 10.0
segmentView.layer.shadowOffset = CGSize(width: 1, height: 1)
segmentView.layer.masksToBounds = false
}
Sample Output:
you forgot to clip ur view.
self.clipsToBounds = true
you need to set maskstobounds to the view layer
segmentView.layer.masksToBounds = true

UITextField rounded corner - swift

Is there anyway to fix this problem and how to put icon inside UITextField?
This is my code.
func roundCorners(corners:UIRectCorner, radius:CGFloat) {
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
let frameLayer = CAShapeLayer()
frameLayer.frame = bounds
frameLayer.path = maskPath.cgPath
frameLayer.strokeColor = UIColor.darkGray.cgColor
frameLayer.fillColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 0).cgColor
self.layer.addSublayer(frameLayer)
}
func roundTopCornersRadius(radius:CGFloat) {
self.roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight], radius:radius)
}
func roundBottomCornersRadius(radius:CGFloat) {
self.roundCorners(corners: [UIRectCorner.bottomLeft, UIRectCorner.bottomRight], radius:radius)
}
in viewDidLoad()
username.roundTopCornersRadius(radius: 8)
password.roundBottomCornersRadius(radius: 8)
This is what i've got
i want my textField look like this
To add an image, use the leftView property of the UITextField.
Your code for the borders seems fine, but remember to ensure that the layout of the textfield has completed before adding the cornerRadius. You can call view.layoutIfNeeded() to ensure that it has been called. Alternatively (the better solution), you can override layoutSubviews() in your custom textfield and ensure the bounds of the layer is changed when the layout of the textfield changes.
Set the textField's borderStyle to .none.
Swift 3 Code Example
#IBDesignable
class CustomTextField: UITextField {
#IBInspectable
var image: UIImage? {
didSet {
imageView.image = image
}
}
var imageView: UIImageView = UIImageView()
override func awakeFromNib() {
super.awakeFromNib()
self.borderStyle = .none
setupImageView()
}
func roundCorners(corners:UIRectCorner, radius:CGFloat) {
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
let frameLayer = CAShapeLayer()
frameLayer.frame = bounds
frameLayer.path = maskPath.cgPath
frameLayer.strokeColor = UIColor.darkGray.cgColor
frameLayer.fillColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 0).cgColor
self.layer.addSublayer(frameLayer)
}
func setupImageView() {
imageView = UIImageView(frame: CGRect(x:5,y:0,width:35,height:25))
imageView.image = image
imageView.contentMode = .scaleAspectFit
self.leftView = imageView
self.leftViewMode = .always
}
func roundTopCornersRadius(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight], radius:radius)
}
func roundBottomCornersRadius(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.bottomLeft, UIRectCorner.bottomRight], radius:radius)
}
}
You will need to implement IBDesignable View
Hmm this is really custom, not sure if you can do that with native textfield without some work.
Why not doing this instead:
An UIView that will contain your two icons and two textfields next to them :
Just make sure to set no border style to your textfield :
Then set the corner radius of the UIView that contain everything to whatever you want ;) easy mate!

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

Shadow not showing when background color is clear color

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)

Resources