How to add shadows to gradient border layer . refer the below image - ios

How to add a shadow effect to this gradient border.
Here is the sample extension to create a border layer with a specified width. When I tried to add a shadow layer whole UI gets affected.
self.gradientBorder(width: 3, colors: UIColor.defaultGradient, andRoundCornersWithRadius: min(bounds.size.height, bounds.size.width))
extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0),
endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0) {
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
width: bounds.size.width + width, height: bounds.size.height + width)
border.colors = colors.map { $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
let path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.path = path
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.backgroundColor = UIColor.black.cgColor
mask.lineWidth = width
mask.masksToBounds = false
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}

Edit
Minor changes from initial code:
background layer doesn't interfere with added subviews
handles resizing correctly (when called in viewDidLayoutSubviews)
You can do this by adding a shadow properties to the view's layer, and adding another layer as a "background" layer.
After Edit... Here is your UIView extension - slightly modified (see the comments):
extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
private static let kLayerNameBackgroundLayer = "BackgroundLayer"
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0),
endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0,
bgColor: UIColor = .white,
shadowColor: UIColor = .black,
shadowRadius: CGFloat = 5.0,
shadowOpacity: Float = 0.75,
shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0)
) {
let existingBackground = backgroundLayer()
let bgLayer = existingBackground ?? CALayer()
bgLayer.name = UIView.kLayerNameBackgroundLayer
// set its color
bgLayer.backgroundColor = bgColor.cgColor
// insert at 0 to not cover other layers
if existingBackground == nil {
layer.insertSublayer(bgLayer, at: 0)
}
// use same cornerRadius as border
bgLayer.cornerRadius = cornerRadius
// inset its frame by 1/2 the border width
bgLayer.frame = bounds.insetBy(dx: width * 0.5, dy: width * 0.5)
// set shadow properties
layer.shadowColor = shadowColor.cgColor
layer.shadowRadius = shadowRadius
layer.shadowOpacity = shadowOpacity
layer.shadowOffset = shadowOffset
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.name = UIView.kLayerNameGradientBorder
// don't do this
// border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
// width: bounds.size.width + width, height: bounds.size.height + width)
// use this instead
border.frame = bounds
border.colors = colors.map { $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
let path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.path = path
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.backgroundColor = UIColor.black.cgColor
mask.lineWidth = width
mask.masksToBounds = false
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func backgroundLayer() -> CALayer? {
let aLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameBackgroundLayer }
if aLayers?.count ?? 0 > 1 {
fatalError()
}
return aLayers?.first
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}
After Edit... and here's an example in-use:
class GradBorderViewController: UIViewController {
var topGradView: UIView = UIView()
// make bottom grad view a button
var botGradView: UIButton = UIButton()
var topBkgView: UIView = UIView()
var botBkgView: UIView = UIView()
let embededLabel: UILabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
embededLabel.textColor = .red
embededLabel.textAlignment = .center
embededLabel.text = "Label as subview"
botGradView.setTitle("Button", for: [])
botGradView.setTitleColor(.red, for: [])
botGradView.setTitleColor(.lightGray, for: .highlighted)
topGradView.backgroundColor = .clear
botGradView.backgroundColor = .clear
topBkgView.backgroundColor = .yellow
botBkgView.backgroundColor = UIColor(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)
[topBkgView, topGradView, botBkgView, botGradView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
embededLabel.translatesAutoresizingMaskIntoConstraints = false
// embed label in topGradView
topGradView.addSubview(embededLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// yellow background view on top half, dark-red background view on bottom half
topBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
topBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
botBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
botBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
topBkgView.topAnchor.constraint(equalTo: g.topAnchor),
botBkgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
topBkgView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.5),
botBkgView.topAnchor.constraint(equalTo: topBkgView.bottomAnchor),
// each grad border view 75% of width, 80-pt constant height
topGradView.widthAnchor.constraint(equalTo: topBkgView.widthAnchor, multiplier: 0.75),
topGradView.heightAnchor.constraint(equalToConstant: 80.0),
botGradView.widthAnchor.constraint(equalTo: topGradView.widthAnchor),
botGradView.heightAnchor.constraint(equalTo: topGradView.heightAnchor),
// center each grad border view in a background view
topGradView.centerXAnchor.constraint(equalTo: topBkgView.centerXAnchor),
topGradView.centerYAnchor.constraint(equalTo: topBkgView.centerYAnchor),
botGradView.centerXAnchor.constraint(equalTo: botBkgView.centerXAnchor),
botGradView.centerYAnchor.constraint(equalTo: botBkgView.centerYAnchor),
// center the embedded label in the topGradView
embededLabel.centerXAnchor.constraint(equalTo: topGradView.centerXAnchor),
embededLabel.centerYAnchor.constraint(equalTo: topGradView.centerYAnchor),
])
botGradView.addTarget(self, action: #selector(self.testTap(_:)), for: .touchUpInside)
}
#objc func testTap(_ sender: Any?) -> Void {
print("Tapped!")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let a1: [CGFloat] = [173, 97, 222].map({$0 / 255.0})
let a2: [CGFloat] = [0, 198, 182].map({$0 / 255.0})
let c1 = UIColor(red: a1[0], green: a1[1], blue: a1[2], alpha: 1.0)
let c2 = UIColor(red: a2[0], green: a2[1], blue: a2[2], alpha: 1.0)
topGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5
)
botGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5,
shadowColor: .white,
shadowRadius: 12,
shadowOpacity: 0.95,
shadowOffset: CGSize(width: 0.0, height: 0.0)
)
}
}
After Edit... Results:

Related

CATextLayer position and Transformation issue

I am trying to build a control like attached circle image with multiple segment having equal space for each part. Number of segments can change depend upon provided array.
I have developed this so far using CAShapeLayer and UIBezierPath. Also added text in the center of each shape Layer.
I have added my code so far for generating this control.
let circleLayer = CALayer()
func createCircle(_ titleArray:[String]) {
update(bounds: bounds, titleArray: titleArray)
containerView.layer.addSublayer(circleRenderer.circleLayer)
}
func update(bounds: CGRect, titleArray: [String]) {
let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
circleLayer.position = position
circleLayer.bounds = bounds
update(titleArray: titles)
}
func update(titleArray: [String]) {
let center = CGPoint(x: circleLayer.bounds.size.width / 2.0, y: circleLayer.bounds.size.height / 2.0)
let radius:CGFloat = min(circleLayer.bounds.size.width, circleLayer.bounds.size.height) / 2
let segmentSize = CGFloat((Double.pi*2) / Double(titleArray.count))
for i in 0..<titleArray.count {
let startAngle = segmentSize*CGFloat(i) - segmentSize/2
let endAngle = segmentSize*CGFloat(i+1) - segmentSize/2
let midAngle = (startAngle+endAngle)/2
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.random.cgColor
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
bezierPath.addLine(to: center)
shapeLayer.path = bezierPath.cgPath
let height = titleArray[i].height(withConstrainedWidth: radius-20, font: UIFont.systemFont(ofSize: 15))
let frame = shapeLayer.path?.boundingBoxOfPath ?? CGRect.zero
let textLayer = TextLayer()
textLayer.frame = CGRect(x: 0, y: 0, width: 70, height: height)
textLayer.position = CGPoint(x: frame.center.x, y: frame.center.y)
textLayer.fontSize = 15
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .center
textLayer.string = titleArray[i]
textLayer.isWrapped = true
textLayer.backgroundColor = UIColor.black.cgColor
textLayer.foregroundColor = UIColor.white.cgColor
textLayer.transform = CATransform3DMakeRotation(midAngle, 0.0, 0.0, 1.0)
shapeLayer.addSublayer(textLayer)
circleLayer.addSublayer(shapeLayer)
}
}
circleLayer is added as superlayer, containing full area of UIView.
My requirement is to add text centered vertically and horizontally within shape with angle. I am facing issue with centring text within shape while angle is fine.
Thanks
Edit:
If I remove textLayer rotation code, then It look like this image.
Your labels are not "centered" because you're using the geometric center of the wedge bounding-box:
What you need to do is calculate an "inner" circle, with 1/2 of the full radius, and then find the points on that circle to place your labels.
So, first we calculate the circle:
Then bisect each angle and find the point on the circle:
Then calculate the bounding-box for the label (I used max-width of radius * 0.6), put the center of that frame on the point on the circle, and then rotate the text layer:
And the result, without the "guides":
Note: For these images, I used radius * 0.55 - or just slightly further out than exactly 1/2 of the radius - for the "inner circle". This gave me just slightly better appearance, due to the wedges narrowing as we get to the center of the circle. Changing that to radius * 0.6 might even look better.
Here is the code to generate this view:
struct Wedge {
var color: UIColor = .cyan
var label: String = ""
}
class WedgeView: UIView {
var wedges: [Wedge] = []
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
}
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { $0.removeFromSuperlayer() }
setup()
}
func setup() -> Void {
// initialize local variables
var startAngle: CGFloat = 0
var outerRadius: CGFloat = 0.0
var halfRadius: CGFloat = 0.0
// initialize local constants
let viewCenter: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)
let diameter = bounds.width
let fontHeight: CGFloat = ceil(12.0 * (bounds.height / 300.0))
let textLayerFont = UIFont.systemFont(ofSize: fontHeight, weight: .light)
outerRadius = diameter * 0.5
halfRadius = outerRadius * 0.55
let labelMaxWidth:CGFloat = outerRadius * 0.6
startAngle = -(.pi * (1.0 / CGFloat(wedges.count)))
for i in 0..<wedges.count {
let endAngle = startAngle + 2 * .pi * (1.0 / CGFloat(wedges.count))
let shape = CAShapeLayer()
let path: UIBezierPath = UIBezierPath()
path.addArc(withCenter: viewCenter, radius: outerRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.addLine(to: viewCenter)
path.close()
shape.path = path.cgPath
shape.fillColor = wedges[i].color.cgColor
shape.strokeColor = UIColor.black.cgColor
self.layer.addSublayer(shape)
let textLayer = CATextLayer()
textLayer.font = textLayerFont
textLayer.fontSize = fontHeight
let string = wedges[i].label
textLayer.string = string
textLayer.foregroundColor = UIColor.white.cgColor
textLayer.backgroundColor = UIColor.black.cgColor
textLayer.isWrapped = true
textLayer.alignmentMode = CATextLayerAlignmentMode.center
textLayer.contentsScale = UIScreen.main.scale
let bisectAngle = startAngle + ((endAngle - startAngle) * 0.5)
let p = CGPoint.pointOnCircle(center: viewCenter, radius: halfRadius, angle: bisectAngle)
var textLayerframe = CGRect(x: 0, y: 0, width: labelMaxWidth, height: 0)
let h = string.getLableHeight(labelMaxWidth, usingFont: textLayerFont)
textLayerframe.size.height = h
textLayerframe.origin.x = p.x - (textLayerframe.size.width * 0.5)
textLayerframe.origin.y = p.y - (textLayerframe.size.height * 0.5)
textLayer.frame = textLayerframe
self.layer.addSublayer(textLayer)
textLayer.transform = CATransform3DMakeRotation(bisectAngle, 0.0, 0.0, 1.0)
// uncomment this block to show the dashed-lines
/*
let biLayer = CAShapeLayer()
let dash = UIBezierPath()
dash.move(to: viewCenter)
dash.addLine(to: p)
biLayer.strokeColor = UIColor.yellow.cgColor
biLayer.lineDashPattern = [4, 4]
biLayer.path = dash.cgPath
self.layer.addSublayer(biLayer)
*/
startAngle = endAngle
}
// uncomment this block to show the half-radius circle
/*
let tempLayer: CAShapeLayer = CAShapeLayer()
tempLayer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: outerRadius - halfRadius, dy: outerRadius - halfRadius)).cgPath
tempLayer.fillColor = UIColor.clear.cgColor
tempLayer.strokeColor = UIColor.green.cgColor
tempLayer.lineWidth = 1.0
self.layer.addSublayer(tempLayer)
*/
}
}
class WedgesWithRotatedLabelsViewController: UIViewController {
let wedgeView: WedgeView = WedgeView()
var wedges: [Wedge] = []
let colors: [UIColor] = [
UIColor(red: 1.00, green: 0.00, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.50, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
UIColor(red: 1.00, green: 0.50, blue: 0.50, alpha: 1.0),
UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
UIColor(red: 0.50, green: 0.50, blue: 1.00, alpha: 1.0),
]
let labels: [String] = [
"This is long text for Label 1",
"Label 2",
"Longer Label 3",
"Label 4",
"Label 5",
"Label 6",
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
for (c, s) in zip(colors, labels) {
wedges.append(Wedge(color: c, label: s))
}
wedgeView.wedges = wedges
view.addSubview(wedgeView)
wedgeView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
wedgeView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
wedgeView.heightAnchor.constraint(equalTo: wedgeView.widthAnchor),
wedgeView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
wedgeView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
}
}
Couple of "helper" extensions used in the above code:
// get a random color
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1),
alpha: 1.0)
}
}
// get the point on a circle at specific radian
extension CGPoint {
static func pointOnCircle(center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint {
let x = center.x + radius * cos(angle)
let y = center.y + radius * sin(angle)
return CGPoint(x: x, y: y)
}
}
// get height of word-wrapping string with max-width
extension String {
func getLableHeight(_ forWidth: CGFloat, usingFont: UIFont) -> CGFloat {
let constraintRect = CGSize(width: forWidth, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: usingFont], context: nil)
return ceil(boundingBox.height)
}
}

How to skip an area (cutout) when drawing with core graphics

I want to draw some items but leave a true alpha transparency cutout for a circular area. What I want to achieve:
Yellow is example background to show bleed through.
The cutout width is actually wider than the arc stroke, so they don't fully intersect. I need true cutout because I a saving to an image with transparency.
I thought maybe I could use setBlendMode() but I believe that would only work if I wanted my cutout to be exactly the same width as the arc. But there is the gist of how I was trying to go about it:
A Swift workbook follows. Any tips on achieving this are greatly appreciated.
import Foundation
import UIKit
var dimen: CGFloat = 200.0;
var strokeWidth: CGFloat = 20.0;
var cutoutWidth: CGFloat = 30.0;
class DonutView : UIView
{
override func draw(_ rect: CGRect)
{
// cutout
let cutoutColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
cutoutColor.setFill()
let cutoutPath = UIBezierPath(ovalIn: CGRect(x: dimen-cutoutWidth, y: dimen/2-cutoutWidth/2, width: cutoutWidth, height: cutoutWidth))
cutoutPath.fill()
// let context = UIGraphicsGetCurrentContext()!
// context.setBlendMode(.sourceOut)
let ringOffset = cutoutWidth/2;
let circleWidth = dimen - ringOffset*2;
// ring
let ringPath = UIBezierPath(ovalIn: CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth))
let ringColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
ringColor.setStroke()
ringPath.lineWidth = strokeWidth
ringPath.stroke()
// arc
let arcRect = CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth)
let arcPath = UIBezierPath()
arcPath.addArc(withCenter: CGPoint(x: arcRect.midX, y: arcRect.midY), radius: arcRect.width / 2, startAngle: -90 * CGFloat.pi/180, endAngle: 37 * CGFloat.pi/180, clockwise: true)
let arcColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
arcColor.setStroke()
arcPath.lineWidth = strokeWidth
arcPath.stroke()
}
}
var view = DonutView(frame: CGRect.init(x: 0, y: 0, width: dimen, height: dimen))
view.backgroundColor = UIColor.yellow
// View these elements
view
(Edit: I should have stated this initially: this is to ultimately create a UIImage for WatchKit)
With help from How to clear circle in CGContext in iOS
import Foundation
import UIKit
import PlaygroundSupport
var dimen: CGFloat = 200.0;
var strokeWidth: CGFloat = 20.0;
var cutoutWidth: CGFloat = 30.0;
class DonutView : UIImageView
{
override init(frame: CGRect) {
super.init(frame: frame)
UIGraphicsBeginImageContextWithOptions(CGSize(width: dimen, height: dimen), false, 1)
let context = UIGraphicsGetCurrentContext()!
let ringOffset = cutoutWidth/2;
let circleWidth = dimen - ringOffset*2;
// ring
let ringPath = UIBezierPath(ovalIn: CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth))
let ringColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
ringColor.setStroke()
ringPath.lineWidth = strokeWidth
ringPath.stroke()
// arc
let arcRect = CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth)
let arcPath = UIBezierPath()
arcPath.addArc(withCenter: CGPoint(x: arcRect.midX, y: arcRect.midY), radius: arcRect.width / 2, startAngle: -90 * CGFloat.pi/180, endAngle: 37 * CGFloat.pi/180, clockwise: true)
let arcColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
arcColor.setStroke()
arcPath.lineWidth = strokeWidth
arcPath.stroke()
// Cutout circle
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.clear)
context.addEllipse(in: CGRect(x: dimen-cutoutWidth, y: dimen/2-cutoutWidth/2, width: cutoutWidth, height: cutoutWidth))
context.drawPath(using: .fill)
context.setBlendMode(.normal)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var view = DonutView(frame: CGRect.init(x: 0, y: 0, width: dimen, height: dimen))
view.backgroundColor = UIColor.yellow
// View these elements
view
You can do this by using another CAShapeLayer as a mask.
The portion(s) of the mask layer that are alpha = 1.0 will be fully transparent.
So...
If we make the Arc Layer a sublayer of the Ring Layer, we can then apply the Cutout Layer as a mask, resulting in:
Here is source for a Playground page:
class MyDonutView : UIView
{
let ringLayer = CAShapeLayer()
let arcLayer = CAShapeLayer()
let cutoutLayer = CAShapeLayer()
var strokeWidth: CGFloat = 20.0;
var cutoutWidth: CGFloat = 30.0;
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add arcLayer as a sublayer of ringLayer
ringLayer.addSublayer(arcLayer)
// add ringLayer as a sublayer of self.layer
layer.addSublayer(ringLayer)
// ring layer stroke is black at 0.3 alpha, fill is clear
ringLayer.strokeColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3).cgColor
ringLayer.fillColor = UIColor.clear.cgColor
ringLayer.lineWidth = strokeWidth
// arc layer stroke is black at 0.6 alpha, fill is clear
arcLayer.strokeColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.6).cgColor
arcLayer.lineWidth = strokeWidth
arcLayer.fillColor = UIColor.clear.cgColor
// cutout layer stroke is black (although we're using Zero line width
// fill is black
cutoutLayer.strokeColor = UIColor.red.cgColor
cutoutLayer.lineWidth = 0
cutoutLayer.fillColor = UIColor.red.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
// define the "padding" around the ring
let ringOffset = cutoutWidth / 2.0
// define the diameter of the ring
let circleWidth = bounds.size.width - cutoutWidth;
// ring path
let ringPath = UIBezierPath(ovalIn: CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth))
// arc path
let arcRect = CGRect(x: ringOffset, y: ringOffset, width: circleWidth, height: circleWidth)
let arcPath = UIBezierPath()
arcPath.addArc(withCenter: CGPoint(x: arcRect.midX, y: arcRect.midY), radius: arcRect.width / 2, startAngle: -90 * CGFloat.pi/180, endAngle: 37 * CGFloat.pi/180, clockwise: true)
// set ring layer path
ringLayer.path = ringPath.cgPath
// set arc layer path
arcLayer.path = arcPath.cgPath
// create a rect path the full size of bounds of self
let fullPath = UIBezierPath(rect: bounds)
// create a cutout path (the small circle to cut-out of the ring/arc)
let cutoutPath = UIBezierPath(ovalIn: CGRect(x: bounds.size.width-cutoutWidth, y: bounds.size.width/2-cutoutWidth/2, width: cutoutWidth, height: cutoutWidth))
// append the cutout path to the full rect path
fullPath.append(cutoutPath)
// even-odd winding rule
cutoutLayer.fillRule = CAShapeLayerFillRule.evenOdd
// set cutout layer path
cutoutLayer.path = fullPath.cgPath
// use cutout layer to mask ring layer
ringLayer.mask = cutoutLayer
}
}
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// instantiate a MyDonutView
let myDonutView = MyDonutView()
// we can set the stroke and cutout widths here
myDonutView.strokeWidth = 20.0
myDonutView.cutoutWidth = 30.0
// we're using auto-layout
myDonutView.translatesAutoresizingMaskIntoConstraints = false
// background color yellow to see the frame
//myDonutView.backgroundColor = .yellow
// otherwise, it should be clear
myDonutView.backgroundColor = .clear
// add as subview
view.addSubview(myDonutView)
// constrain centerX and centerY
// width = 200, height = width
NSLayoutConstraint.activate([
myDonutView.widthAnchor.constraint(equalToConstant: 200.0),
myDonutView.heightAnchor.constraint(equalTo: myDonutView.widthAnchor),
myDonutView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myDonutView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
let vc = TestViewController()
PlaygroundPage.current.liveView = vc

How to draw multiple horizontally circles in rectangle (UIButton or UIControl) Swift iOS

How to draw about three circle in horizontally area with main and ring color in rectangle. I need to create custom button with this circles, something like this:
Is there any good way to do this?
We can design such kind of views with UIStackView in very ease manner.
Take a stackView, set its alignment to center, axis to horizontal and distribution to fill. Create a UILabel/UIButton/UIImageView or even UIView and add rounded radius and border to it. Finally, add those views to the main stackView.
Try this.
override func viewDidLoad() {
super.viewDidLoad()
//Setup stackView
let myStackView = UIStackView()
myStackView.axis = .horizontal
myStackView.alignment = .center
myStackView.distribution = .fillEqually
myStackView.spacing = 8
view.addSubview(myStackView)
//Setup circles
let circle_1 = circleLabel()
let circle_2 = circleLabel()
let circle_3 = circleLabel()
myStackView.addArrangedSubview(circle_1)
myStackView.addArrangedSubview(circle_2)
myStackView.addArrangedSubview(circle_3)
myStackView.translatesAutoresizingMaskIntoConstraints = false
myStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
myStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
}
func circleLabel() -> UILabel {
let label = UILabel()
label.backgroundColor = UIColor.red
label.layer.cornerRadius = 12.5
label.layer.masksToBounds = true
label.layer.borderColor = UIColor.orange.cgColor
label.layer.borderWidth = 3.0
label.widthAnchor.constraint(equalToConstant: 25.0).isActive = true
label.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
return label
}
To make a Single Circle like that, you need to make use of UIBezierPath and CAShapeLayer .
let outerCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(50), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let outerCircleShapeLayer = CAShapeLayer()
outerCircleShapeLayer.path = outerCirclePath.cgPath
outerCircleShapeLayer.fillColor = UIColor.white.cgColor
outerCircleShapeLayer.lineWidth = 3.0
view.layer.addSublayer(outerCircleShapeLayer)
// Drawing the inner circle
let innerCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(40), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let innerCircleShapeLayer = CAShapeLayer()
innerCircleShapeLayer.path = innerCirclePath.cgPath
innerCircleShapeLayer.fillColor = UIColor.blue.cgColor
view.layer.addSublayer(innerCircleShapeLayer)
I have attached an image below for the Playground version of it .
Just play around with arcCenter and radius values and you will get the desired output
My team helped me and here is solution to create this with dynamically changing state of circles (with different stroke and fill colors):
import UIKit
#IBDesignable
class CirclesButton: UIControl {
#IBInspectable
var firstCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
#IBInspectable
var secondCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
#IBInspectable
var thirdCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
// get context
guard let context = UIGraphicsGetCurrentContext() else { return }
// make configurations
context.setLineWidth(1.0);
context.setStrokeColor(UIColor.white.cgColor)
context.setFillColor(red: 0.0, green: 0.58, blue: 1.0, alpha: 1.0)
// find view center
let dotSize:CGFloat = 11.0
let viewCenter = CGPoint(x: rect.midX, y: rect.midY)
// find personal dot rect
var dotRect = CGRect(x: viewCenter.x - dotSize / 2.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if secondCircle {
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
// find global notes rect
dotRect = CGRect(x: viewCenter.x - dotSize * 1.5 - 4.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if firstCircle {
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
// find music rect
dotRect = CGRect(x: viewCenter.x + dotSize / 2.0 + 4.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if thirdCircle {
context.setFillColor(red: 0.0, green: 1.0, blue: 0.04, alpha: 1.0)
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
}
}
It will looks like: CirclesButton
Сode:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let buttonSize: CGFloat = 80
let firstButton = CustomButton(position: CGPoint(x: 0, y: 0), size: buttonSize, color: .blue)
self.view.addSubview(firstButton)
let secondButton = CustomButton(position: CGPoint(x: firstButton.frame.maxX, y: 0), size: buttonSize, color: .blue)
self.view.addSubview(secondButton)
let thirdButton = CustomButton(position: CGPoint(x: secondButton.frame.maxX, y: 0), size: buttonSize, color: .green)
self.view.addSubview(thirdButton)
}
}
class CustomButton: UIButton {
init(position: CGPoint, size: CGFloat, color: UIColor) {
super.init(frame: CGRect(x: position.x, y: position.y, width: size, height: size))
self.backgroundColor = color
self.layer.cornerRadius = size / 2
self.clipsToBounds = true
self.layer.borderWidth = 4.0 // make it what ever you want
self.layer.borderColor = UIColor.white.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
You can handle button tapped like:
override func viewDidLoad() {
super.viewDidLoad()
firstButton.addTarget(self, action: #selector(handleFirstButton), for: .touchUpInside)
}
#objc func handleFirstButton(sender: UIButton) {
print("first button tapped")
}
Best and Universal Solution for **Button or Label creation (Fully Dynamic)**
var x = 10
var y = 5
var buttonHeight = 40
var buttonWidth = 40
for i in 0..<3 {
let roundButton = UIButton(frame: CGRect(x: x, y: y, width: buttonWidth, height: buttonHeight))
roundButton.setTitle("Butt\(i)", for: .normal)
roundButton.layer.cornerRadius = roundButton.bounds.size.height/2
yourButtonBackView.addSubview(roundButton)
x = x + buttonWidth + 10
if x >= Int(yourButtonBackView.frame.width - 30) {
y = y + buttonHeight + 10
x = 10
}
}

Set Background Gradient on Button in Swift

I have no idea how to set the background gradient on a button (without making the background gradient an image). This is so different from Android.
Here's a class I have to define a returnable gradient scheme:
import UIKit
extension CAGradientLayer {
func backgroundGradientColor() -> CAGradientLayer {
let topColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(51/255.0), alpha: 1)
let bottomColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(255/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
I can use this to set the background of my entire view with the following:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let background = CAGradientLayer().backgroundGradientColor()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
}
//...
}
But how can I access the view of the button and insert the sublayer or something like that?
Your code works fine. You just have to remember to set the gradient's frame every time. It is better to just make the gradient category also set the frame of the view for you.
That way you don't forget and it applies fine.
import UIKit
extension UIView {
func applyGradient(colours: [UIColor]) -> CAGradientLayer {
return self.applyGradient(colours: colours, locations: nil)
}
func applyGradient(colours: [UIColor], locations: [NSNumber]?) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
return gradient
}
}
class ViewController: UIViewController {
#IBOutlet weak var btn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.btn.applyGradient(colours: [.yellow, .blue])
self.view.applyGradient(colours: [.yellow, .blue, .red], locations: [0.0, 0.5, 1.0])
}
}
Buttons are views. You apply gradients to it the same way you would apply it to any other view.
Picture Proof:
Video Proof:
https://i.imgur.com/ssDTqPu.mp4
It's this simple:
import UIKit
class ActualGradientButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let l = CAGradientLayer()
l.frame = self.bounds
l.colors = [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor]
l.startPoint = CGPoint(x: 0, y: 0.5)
l.endPoint = CGPoint(x: 1, y: 0.5)
l.cornerRadius = 16
layer.insertSublayer(l, at: 0)
return l
}()
}
Here below you can find the solution for Swift3 (and Swift4 too) and a little bit extended (orientation helper):
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint : CGPoint {
return points.startPoint
}
var endPoint : CGPoint {
return points.endPoint
}
var points : GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0,y: 1.0), CGPoint(x: 1.0,y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 1,y: 1))
case .horizontal:
return (CGPoint(x: 0.0,y: 0.5), CGPoint(x: 1.0,y: 0.5))
case .vertical:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 0.0,y: 1.0))
}
}
}
extension UIView {
func applyGradient(with colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(with colours: [UIColor], gradient orientation: GradientOrientation) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
#Zeb answer is great but just to clean it up and make it a little more swifty.
Computed read-only properties should avoid using get and returning Void is redundant:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
}
}
}
extension UIView {
func applyGradient(withColours colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
If you want a gradient background on a button, rather than adding the gradient as a sublayer and changing its frame in layoutSubviews, I would instead just specify the layerClass of the button to be a CAGradientLayer, so the main layer is a gradient:
#IBDesignable
public class GradientButton: UIButton {
public override class var layerClass: AnyClass { CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
#IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
#IBInspectable public var endColor: UIColor = .red { didSet { updateColors() } }
// expose startPoint and endPoint to IB
#IBInspectable public var startPoint: CGPoint {
get { gradientLayer.startPoint }
set { gradientLayer.startPoint = newValue }
}
#IBInspectable public var endPoint: CGPoint {
get { gradientLayer.endPoint }
set { gradientLayer.endPoint = newValue }
}
// while we're at it, let's expose a few more layer properties so we can easily adjust them in IB
#IBInspectable public var cornerRadius: CGFloat {
get { layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
#IBInspectable public var borderWidth: CGFloat {
get { layer.borderWidth }
set { layer.borderWidth = newValue }
}
#IBInspectable public var borderColor: UIColor? {
get { layer.borderColor.flatMap { UIColor(cgColor: $0) } }
set { layer.borderColor = newValue?.cgColor }
}
// init methods
public override init(frame: CGRect = .zero) {
super.init(frame: frame)
updateColors()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
updateColors()
}
}
private extension GradientButton {
func updateColors() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
}
}
By setting the layerClass, it will just make the main layer be a gradient, which automatically is adjusted to the bounds of the button for you. This has an advantage that if you animate the changing of the button size (e.g. on rotation events or whatever), the gradient will be correctly animated, too.
And, it is not necessary, but it may be convenient to make this class an #IBDesignable, so one can set its properties in IB, and it will be correctly rendered in the storyboard/NIB with no additional code in the view controller. For example, I can customize the corners, border, and gradient colors and direction in IB:
Try this is working for me ,
let button = UIButton(frame: CGRect(x: 60, y: 150, width: 200, height: 60))
button.setTitle("Email", for: .normal)
button.backgroundColor = .red
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
// Apply Gradient Color
let gradientLayer:CAGradientLayer = CAGradientLayer()
gradientLayer.frame.size = button.frame.size
gradientLayer.colors =
[UIColor.white.cgColor,UIColor.green.withAlphaComponent(1).cgColor]
//Use diffrent colors
button.layer.addSublayer(gradientLayer)
self.view.addSubview(button)
You can add starting and end point of gradient color.
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
For more details description refer CAGradientLayer doc
I have tried all of them this is my button init inside of viewdidload
let button = UIButton()
button.setTitle("Alper", for: .normal)
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
view.addSubview(button)
button.anchor(top: nil, left: nil, bottom: logo.topAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 50, width: 100)
let gradientx = CAGradientLayer()
gradientx.colors = [UIColor.blue,UIColor.red]
gradientx.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientx.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientx.frame = button.bounds
button.layer.insertSublayer(gradientx, at: 0)
anchor is an extension, so this is irrelevant gradient.
There are already many answers there I want add what I did to achieve this. I use this custom Button GradientButton
import Foundation
import UIKit
class GradientButton: UIButton {
let gradientColors : [UIColor]
let startPoint : CGPoint
let endPoint : CGPoint
required init(gradientColors: [UIColor] = [UIColor.red, UIColor.blue],
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.gradientColors = gradientColors
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let halfOfButtonHeight = layer.frame.height / 2
contentEdgeInsets = UIEdgeInsets(top: 10, left: halfOfButtonHeight, bottom: 10, right: halfOfButtonHeight)
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
backgroundColor = UIColor.clear
// setup gradient
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = gradientColors.map { $0.cgColor }
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.cornerRadius = 4
// replace gradient as needed
if let oldGradient = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldGradient, with: gradient)
} else {
layer.insertSublayer(gradient, below: nil)
}
// setup shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: halfOfButtonHeight).cgPath
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowOpacity = 0.85
layer.shadowRadius = 4.0
}
override var isHighlighted: Bool {
didSet {
let newOpacity : Float = isHighlighted ? 0.6 : 0.85
let newRadius : CGFloat = isHighlighted ? 6.0 : 4.0
let shadowOpacityAnimation = CABasicAnimation()
shadowOpacityAnimation.keyPath = "shadowOpacity"
shadowOpacityAnimation.fromValue = layer.shadowOpacity
shadowOpacityAnimation.toValue = newOpacity
shadowOpacityAnimation.duration = 0.1
let shadowRadiusAnimation = CABasicAnimation()
shadowRadiusAnimation.keyPath = "shadowRadius"
shadowRadiusAnimation.fromValue = layer.shadowRadius
shadowRadiusAnimation.toValue = newRadius
shadowRadiusAnimation.duration = 0.1
layer.add(shadowOpacityAnimation, forKey: "shadowOpacity")
layer.add(shadowRadiusAnimation, forKey: "shadowRadius")
layer.shadowOpacity = newOpacity
layer.shadowRadius = newRadius
let xScale : CGFloat = isHighlighted ? 1.025 : 1.0
let yScale : CGFloat = isHighlighted ? 1.05 : 1.0
UIView.animate(withDuration: 0.1) {
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
}
}
}
}
You can make GradientButton instance like this.
let button = GradientButton.init(gradientColors:[UIColor.black, UIColor.white], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: 1))
For Swift
extension UIViewController {
func makeGradientColor(`for` object : AnyObject , startPoint : CGPoint , endPoint : CGPoint) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [(UIColor.red.cgColor), (UIColor.yellow.cgColor)]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = CGRect(x: 0.0, y: 0.0, width: object.bounds.size.width, height: object.bounds.size.height)
return gradient
}
}
How to use ?
if let layers = btn.layer.sublayers{
for layer in layers {
if layer.isKind(of: CAGradientLayer.self) {
layer.removeFromSuperlayer()
}
}
}
let start : CGPoint = CGPoint(x: 0.0, y: 0.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)
let gradient: CAGradientLayer = self.makeGradientColor(for: cell.bgView, startPoint: start, endPoint: end)
btn.layer.insertSublayer(gradient, at: 0)
I've modified this great answer to improve the reusability of the button by adding init parameters for colors, radius, and gradient direction.
I also added updateGradientColors method as it might be useful if you want to change the gradient color at some point.
class GradientButton: UIButton {
private let colors: [UIColor]
private let cornerRadius: CGFloat
private let startPoint: CGPoint
private let endPoint: CGPoint
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
init(colors: [UIColor],
cornerRadius: CGFloat = 10,
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.colors = colors
self.cornerRadius = cornerRadius
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let gl = CAGradientLayer()
gl.frame = self.bounds
gl.colors = colors.map { $0.cgColor }
gl.startPoint = startPoint
gl.endPoint = endPoint
gl.cornerRadius = cornerRadius
layer.insertSublayer(gl, at: 0)
return gl
}()
func updateGradientColors(_ colors: [UIColor]) {
gradientLayer.colors = colors.map { $0.cgColor }
}
}
Gradient Button with corner radius, start and End Points Code is here...
extension UIView {
func applyGradient(colours: [UIColor], cornerRadius: CGFloat?, startPoint: CGPoint, endPoint: CGPoint) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
if let cornerRadius = cornerRadius {
gradient.cornerRadius = cornerRadius
}
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.colors = colours.map { $0.cgColor }
self.layer.insertSublayer(gradient, at: 0)
}
}
Usage :
self.yourButton.applyGradient(colours: [.red, .green], cornerRadius: 20, startPoint: CGPoint(x: 0, y: 0.5), endPoint: CGPoint(x: 1, y: 0.5))
class ButtonGradient : UIButton {
override func layoutSubviews() {
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPoint(x: 0, y: 0)
// layer.cornerRadius = CGFloat(frame.width / 20)
let color0 = UIColor(red:255/255, green:122/255, blue:0/255, alpha:1.0).cgColor
let color1 = UIColor(red:255/255, green:176/255, blue: 0/255, alpha:1.0).cgColor
let color2 = UIColor(red:250/255, green:98/255, blue: 44/255, alpha:1.0).cgColor
layer.locations = [0.5, 1.0]
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 0.5, y: 0.5)
layer.colors = [color2,color0,color1]
self.layer.insertSublayer(layer, at: 0)
}
}
After that directly assign "ButtonGredient" class to particular button in Storyboard.
Here, I have taken one UIView and add button in it.
#IBOutlet weak var btnCenter: UIButton!
#IBOutlet weak var viewCenter: UIView!
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.yello.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewCenter.bounds
// add the gradient layer to the views layer for rendering
viewCenter.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewCenter.mask = btnCenter
//Set corner Radius and border Width of button
btnCenter.layer.cornerRadius = btnCenter.frame.size.height / 2
btnCenter.layer.borderWidth = 5.0
There are ways to work with initial layer without making sublayers.
import UIKit
#IBDesignable class GradientButton: UIButton {
#IBInspectable var startColor: UIColor = UIColor.white
#IBInspectable var endColor: UIColor = UIColor.white
#IBInspectable var cornerRadius = CGFloat(5.0)
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
//This is an advanced gradient we do not use for now
// (layer as! CAGradientLayer).startPoint = CGPoint(x: 0, y: 0)
// (layer as! CAGradientLayer).endPoint = CGPoint(x: 1, y: 1)
// (layer as! CAGradientLayer).locations = [0,1]
// Simple gradient
(layer as! CAGradientLayer).colors = [startColor.cgColor, endColor.cgColor]
layer.cornerRadius = cornerRadius
}
}
class GradientButton: UIButton {
var gradientLayer: CAGradientLayer? {
didSet {
layer.sublayers?.filter { $0 is CAGradientLayer }.forEach { $0.removeFromSuperlayer() }
if let gradientLayer = gradientLayer {
layer.insertSublayer(gradientLayer, at: 0)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.frame = self.bounds
}
}

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