UIScrollView zooming and affected CAShapeLayer lineWidth - ios

I have a bunch of CAShapeLayers built with UIBezierPath and stored inside of UIScrollView.
I wonder if there any way to keep the lineWidth of CAShapeLayer's path away from scaling when UIScrollView is zoomed over 100%. I mean applying the UIScrollView's transformation rules to CAShapeLayer, but keep the path's widh the same as it used to be on 1.0 zoomScale. (So when the layer with 1pt path lineWidth will be zoomed to 300%, the lineWidth of the layer on #2x device won't become 6px height, but will keep the height of 2px)

class ViewController : UIViewController, UIScrollViewDelegate {
private lazy var scrollView = UIScrollView()
private lazy var contentView = UIView()
private lazy var shapeLayer = CAShapeLayer()
private let lineWidth: CGFloat = 1
override func loadView() {
view = UIView()
view.addSubview(scrollView)
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.addSubview(contentView)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
scrollView.delegate = self
contentView.backgroundColor = .red
let viewSize = CGSize(width: 400, height: 400)
scrollView.contentSize = viewSize
contentView.frame = CGRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)
setUpShapeLayer(in: viewSize)
}
//MARK: - UIScrollViewDelegate
func scrollViewDidZoom(_ scrollView: UIScrollView) {
shapeLayer.lineWidth = lineWidth * 1 / scrollView.zoomScale
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return contentView
}
//MARK: - Private
private func setUpShapeLayer(in size: CGSize) {
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = lineWidth
contentView.layer.addSublayer(shapeLayer)
let shapeSize = CGSize(width: 200, height: 200)
let shapeBounds = CGRect(origin: .zero, size: shapeSize)
shapeLayer.bounds.size = shapeSize
shapeLayer.path = UIBezierPath(ovalIn: shapeBounds).cgPath
shapeLayer.position = CGPoint(x: size.width / 2, y: size.height / 2)
}
}
Is it what you want ?

Related

UiImageView blurs after zooming

I have a UIImageView inside a scrollView. Its initial width and height are equal to 100. I want this UIImageView to appear in same size even after zooming. For that I modify its size inside scrollViewDidEndZooming method. This is working fine without a problem. However UiImage of the UiImageView looks blurry when zoom scale is increased. I tried to set contentScale property of the UiImageView, but it is not working.
How I can fix this?
import UIKit
class ViewController: UIViewController {
static var zoomScale: CGFloat = 1.0
let scrollView = UIScrollView()
let contentView = UIView()
let imageView: UIImageView = UIImageView()
let imageViewSize: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
self.setupScrollView()
self.setuContentView()
self.addImageView()
}
func setupScrollView() {
let vWidth = self.view.frame.width
let vHeight = self.view.frame.height
scrollView.delegate = self
scrollView.frame = CGRect(x: 0, y: 0, width: vWidth, height: vHeight)
scrollView.backgroundColor = .lightGray
scrollView.alwaysBounceVertical = false
scrollView.alwaysBounceHorizontal = false
scrollView.showsVerticalScrollIndicator = true
scrollView.flashScrollIndicators()
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
self.view.addSubview(scrollView)
}
func setuContentView() {
contentView.frame = scrollView.frame.insetBy(dx: 20, dy: 20)
contentView.backgroundColor = .white
scrollView.addSubview(contentView)
}
func addImageView(){
let image = UIImage(systemName: "arrow.triangle.2.circlepath")
imageView.frame = CGRect(x: 200, y: 200, width: imageViewSize, height: imageViewSize)
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .systemBlue
imageView.tintColor = .white
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 0.5 * imageView.bounds.size.width // I want rounded imageView
self.contentView.addSubview(imageView)
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.contentView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
ViewController.zoomScale = scale
imageView.frame = CGRect(x: imageView.frame.origin.x, y: imageView.frame.origin.y, width: imageViewSize/scale, height: imageViewSize/scale)
imageView.layer.cornerRadius = 0.5 * imageView.bounds.size.width
imageView.layer.contentsScale = scale
}
}

Shadow not visible on top of "sibling" view

I'm trying to create a shadow on the top a view. This is my hierarchy:
View
------TablewView
------BottomView
TableView and BottomView have the same parent: View.
I want the BottomView to have a shadow on top of the TableView like the pic on the left, but the result is the one on the right:
If I try to remove the TableView I see the shadow.
The BottomView haves rounded corners.
This is the BottomView Class:
class BottomView: UIView {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
let shadowLayer = CAShapeLayer()
shadowLayer.masksToBounds = false
//rect is an ex.
shadowLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 200, height: 80), cornerRadius: 9).cgPath
shadowLayer.fillColor = UIColor.red.cgColor
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 5, height: -5)
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 3
shadowLayer.zPosition = 10
layer.insertSublayer(shadowLayer, at: 0)
clipsToBounds = false
}
}
}
The first issue is variable / object scope...
class BottomView: UIView {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
let shadowLayer = CAShapeLayer()
...
Where you say: let shadowLayer = CAShapeLayer(), you just created a new, local shadowLayer object, and it disappears when it goes out of scope (at the end of the if block).
Change that line to:
shadowLayer = CAShapeLayer() // remove the let
and you should see your shadow.
You can "automate" the sizing, by adding this below the if block:
if shadowLayer != nil {
shadowLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height), cornerRadius: 9).cgPath
}
that will re-size the shadow layer anytime the view changes size.

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

Swift textfields without border

I am new to swift. Your help will be really appreciated.
I have two textfields in my application. How would I create same UI as given in the pic below.
I want to create textfields with only one below border as given in the screenshot.
https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
#IBOutlet var textField: UITextField! {
didSet {
let border = CALayer()
let width: CGFloat = 1 // this manipulates the border's width
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width,
width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
}
Create a subclass of UITextField so you can reuse this component across multiple views without have to re implement the drawing code. Expose various properties via #IBDesignable and #IBInspectable and you can have control over color and thickness in the story board. Also - implement a "redraw" on by overriding layoutSubviews so the border will adjust if you are using auto layout and there is an orientation or perhaps constraint based animation. That all said - effectively your subclass could look like this:
import UIKit
class Field: UITextField {
private let border = CAShapeLayer()
#IBInspectable var color: UIColor = UIColor.blue {
didSet {
border.strokeColor = color.cgColor
}
}
#IBInspectable var thickness: CGFloat = 1.0 {
didSet {
border.lineWidth = thickness
}
}
override func draw(_ rect: CGRect) {
self.borderStyle = .none
let from = CGPoint(x: 0, y: rect.height)
let here = CGPoint(x: rect.width, y: rect.height)
let path = borderPath(start: from, end: here).cgPath
border.path = path
border.strokeColor = color.cgColor
border.lineWidth = thickness
border.fillColor = nil
layer.addSublayer(border)
}
override func layoutSubviews() {
super.layoutSubviews()
let from = CGPoint(x: 0, y: bounds.height)
let here = CGPoint(x: bounds.width, y: bounds.height)
border.path = borderPath(start: from, end: here).cgPath
}
private func borderPath(start: CGPoint, end: CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
return path
}
}
Then when you add a text field view to your story board - update the class in the Identity Inspector to use this subclass, Field - and then in the attributes inspector, you can set color and thickness.
Add border at Bottom in UITextField call below function:
func setTextFieldBorder(_ dimension: CGRect) -> CALayer {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: dimension.size.height - width, width: dimension.size.width, height: dimension.size.height)
border.borderWidth = width
return border
}
How to set UITextField border in textField below sample code for that:
txtDemo.layer.addSublayer(setTextFieldBorder(txtDemo.frame))
txtDemo.layer.masksToBounds = true
Where txtDemo is IBOutlet of UITextField.

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