UIView shadow issue on real device - iOS - ios

I have a tableview cell with view inside and I want to add shadow to it.
For this reason I add this code:
self.PrimaView.layer.shadowPath = UIBezierPath(rect: self.PrimaView.bounds).cgPath
self.PrimaView.layer.shadowRadius = 3
self.PrimaView.layer.shadowOffset = .zero
self.PrimaView.layer.shadowOpacity = 3
This is tableview cell structure:
My problem is this:
when I execute code on simulator I get this (this is what I want to get):
but when I execute the same code on the real device I get this:
Why is it different than the previous image?

#Sulthan is right or may be you have safe area issue as well if you have same device if not.
Then you can use this code for shadow
import UIKit
#IBDesignable
class CardViewMaterial: UIView {
// #IBInspectable var cornerRadius: CGFloat = 2
#IBInspectable var shadowOffsetWidth: Int = 0
#IBInspectable var shadowOffsetHeight: Int = 3
#IBInspectable var shadowColor: UIColor? = UIColor.black
#IBInspectable var shadowOpacity: Float = 0.5
override func layoutSubviews() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
}
}

Related

How to animate the shadow border of an UIView at the same time its content grows

I am working on a project which has all views embedded in a view container like this:
Today, I have to make one of these containers grow vertically in a animatable way when a stack view is showing its arranged subviews. So, the first problem was that the shadow border of this container is not being animated and I realized that the shadow path should be animated in another way and not just in a UIView.animate block.
I have isolated the problem in a small project:
As you can see, I am trying to deploy/undeploy the container view depending on the inner stackview arranged subviews. The problem is the shadow path, which I am already trying to understand how it works.
Can you help me to understand how to manage a UIView shadow layer when you need to update the height of an UIView in an animation block?
Container Shadow view code:
import UIKit
#IBDesignable final class ShadowView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowColor: UIColor = UIColor.darkGray {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetWidth: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetHeight: CGFloat = 1.8 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOpacity: Float = 0.30 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowRadius: CGFloat = 3.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var isAnimatable: Bool = false
private var shadowLayer: CAShapeLayer = CAShapeLayer() {
didSet {
setNeedsLayout()
}
}
private var previousPat: CGPath = CGPath(rect: CGRect(x: 0, y: 0, width: 0, height: 0), transform: .none)
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
shadowLayer.path = UIBezierPath(roundedRect: bounds,
cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = backgroundColor?.cgColor
shadowLayer.shadowColor = shadowColor.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: shadowOffsetWidth,
height: shadowOffsetHeight)
shadowLayer.shadowOpacity = shadowOpacity
shadowLayer.shadowRadius = shadowRadius
if isAnimatable {
let animation = CABasicAnimation(keyPath: "shadowPath")
animation.fromValue = previousPat
animation.toValue = shadowLayer.path
animation.autoreverses = false
animation.duration = 0.8
shadowLayer.add(animation, forKey: "pathAnimation")
}
layer.insertSublayer(shadowLayer, at: 0)
previousPat = shadowLayer.path!
}
}
Main ViewController code:
final class ViewController: UIViewController {
#IBOutlet weak var stack: UIStackView!
#IBOutlet weak var redContainerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func animateAction(_ sender: Any) {
UIView.animate(withDuration: 0.8) {
self.redContainerLabel.alpha = self.redContainerLabel.alpha == 0 ? 1 : 0
self.stack.arrangedSubviews.forEach {
$0.isHidden.toggle()
}
}
}
}
View structure:
I was looking for an answer to exactly this problem and encountered this post here, which I believe is what you're looking for:
Animating CALayer shadow simultaneously as UITableviewCell height animates
The solution is a bit verbose (I'm surprised the use-case is so rare as to not prompt apple to fix(?) it) and a few years old at this point, but the most recent I could find. Ultimately I decided to work around it by fading out my shadows before resizing, but you might find it useful!
A couple of other, older posts I've found appear to corroborated the level of effort required for this effect (again, Apple's got a weird list of things it decides are your problem), so that first post is probably your best bet.
https://petosoft.wordpress.com/2012/10/24/shadowpath-not-animating-with-standard-uiview-animations-on-ipad/
Smoothly rotate and change size of UIView with shadow
UIView: How to animate CALayer frame with shadow?
If you've managed to find a more straightforward solution, by all means let me know; in the mean-time I hope this helps.

Issue in setting corner radius of view inside cell?

I have a custom UITableView cell.I am setting it's bottom left & bottom right corner radius.I am setting corner radius in cellForAtindexPath.Below is the code for that
if indexPath.row == 9 {
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
recipeInfoCell.layoutSubviews()
recipeInfoCell.layoutIfNeeded()
} else {
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 0)
recipeInfoCell.layoutSubviews()
}
Now when i first launch the tableview then it does not set any corner radius. But when i scroll again then it is setting the corner radius.
I have created an extension of UIView in which there is one function which is setting the corner radius
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
Please tell how do i resolve this ?
I do not think it is a good idea to set corner radius in cellForRow atIndexPath. The reason being, this function is called many times during the lifetime of UITableView and you only need to set the corner radius only once and that too when the cell is initialised. Changing the corner radius based on indexPath will also affect the UITableView's performance.
A better way to this would be to create two cells, one with corner radius as 0 and another with 10 and the use those cells based on indexPath.
Then you can put your cornerRadius set logic in layoutSubview function in your custom cell.
If you want to do it in your tableView methods only, the correct way is to do it in willDisplayCell because after that call, cell's layoutSubviews function in called.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row % 2 == 0 {
let path = UIBezierPath(roundedRect: cell.contentView.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
let mask = CAShapeLayer()
mask.path = path.cgPath
cell.contentView.layer.mask = mask
} else {
let path = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 0, height: 0))
let mask = CAShapeLayer()
mask.path = path.cgPath
cell.contentView.layer.mask = mask
}
}
UPDATE: May 19, 2017
The above concept will work fine when the view that you want to round and put shadow on is the same size as the cell's content view. But if it is anything different than that, it won't work.
The reason for the above statement is that at the time when willDisplayCell is called, where the above code is using cell.contentView.bounds, the other views are not calculated yet. So when we will be using another view, we will have to use that view's bounds to calculate the mask's frame which we will be different from the actual one.
After reading up on this a bit, I found out that, to do this kind of a thing is by overriding draw(_ rect: CGRect) function of UITableViewCell. Because at this point, the view's size has been properly calculated and we can create a correct frame.
Below is the code from custom UITableViewCell class:
var shadowLayer = CAShapeLayer()
override func draw(_ rect: CGRect) {
let path = UIBezierPath(roundedRect: self.outerView.bounds, byRoundingCorners: [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.outerView.layer.mask = mask
// Handle Cell reuse case
shadowLayer.removeFromSuperlayer()
shadowLayer.shadowPath = path.cgPath
shadowLayer.frame = self.outerView.layer.frame
print(shadowLayer.frame)
shadowLayer.shadowOffset = CGSize(width: 0, height: 0)
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOpacity = 0.9
self.contentView.layer.insertSublayer(shadowLayer, below: self.outerView.layer)
super.draw(rect)
}
put round corner code in main queue like this :
if indexPath.row == 9 { dispatch_async(dispatch_get_main_queue(),{
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
})} else{
dispatch_async(dispatch_get_main_queue(),{
recipeInfoCell.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 0)
}) }
Try writing these lines of code in layoutSubviews() of your custom UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
self.outerView.roundCorners(corners: [.bottomLeft, .bottomRight], radius: 10)
}
For acquiring that in swift only I use to create one subclass of the UIButton like below
(in any .swift file of project)
//MARK: Custom Class for UIView
open class CustomView: UIView {
open func drawViewsForRect(_ rect: CGRect) {
fatalError("\(#function) must be overridden")
}
open func updateViewsForBoundsChange(_ bounds: CGRect) {
fatalError("\(#function) must be overridden")
}
}
Then define the below methods in same or deferent .swift file like this
//MARK: - UIView Property Class
#IBDesignable open class CView : CustomView{
#IBInspectable dynamic open var borderColor: UIColor = UIColor.clear{
didSet{
updateBorderColor()
}
}
#IBInspectable dynamic open var borderWidth: CGFloat = 1.0{
didSet{
updateBorderWidth()
}
}
#IBInspectable dynamic open var cornerRadius: CGFloat = 0.0{
didSet{
updateBorderRadius()
}
}
#IBInspectable dynamic open var shadowColor: UIColor?{
didSet{
updateShadowColor()
}
}
#IBInspectable dynamic open var shadowRadius: CGFloat = 0.0{
didSet{
updateShadowRadius()
}
}
#IBInspectable dynamic open var shadowOpacity: Float = 0.0{
didSet{
updateShadowOpacity()
}
}
#IBInspectable dynamic open var shadowOffSet: CGSize = CGSize(width: 0.0, height: 0.0){
didSet{
updateShadowOffset()
}
}
//Update Borders Properties
open func updateBorderColor(){
self.layer.borderColor = borderColor.cgColor
}
open func updateBorderRadius(){
self.layer.cornerRadius = cornerRadius
}
open func updateBorderWidth(){
self.layer.borderWidth = borderWidth
}
//Update Shadow Properties
open func updateShadowColor(){
self.layer.shadowColor = shadowColor?.cgColor
self.clipsToBounds = false;
self.layer.masksToBounds = false;
}
open func updateShadowOpacity(){
self.layer.shadowOpacity = shadowOpacity
self.clipsToBounds = false;
self.layer.masksToBounds = false;
}
open func updateShadowRadius(){
self.layer.shadowRadius = shadowRadius
self.clipsToBounds = false;
self.layer.masksToBounds = false;
}
open func updateShadowOffset(){
self.layer.shadowOffset = CGSize(width: shadowOffSet.width, height: shadowOffSet.height)
self.layer.shadowColor = shadowColor?.cgColor
self.clipsToBounds = false;
self.layer.masksToBounds = false;
}
}
Then just assign the CView class in storyboard at design time for any view controller and just provide the required values for the properties for that in side the attribute inspector for that view
In Storyboard
1) Class of the View Like this
2) Set property like this
3) This will show view in side the design like this
With this you even been able to see the shadow or corner radius directly in your design builder i.e. in side the storyboard view like the third image.
You want a specific cell to round corner if i understand correctly. here is my solution.Though i am bit late but i try to help others. :) I just added my solution as a picture.
Step 1:
Created a custom Cell and did some necessary steps.
Below is the code for Rounded bottom left and bottom right.Sorry for poor coding style:
Below is My view controller's configuration for showing tableView with rounded cells.
And Below is the moment of truth:
I took reference from (https://stackoverflow.com/a/44067058/2781088) and modified the code and now it's working for me:
Add below code to your custom cell class:
enum cellStyle: Int {
case Normal = 0, Rounded
}
class CustomTableCell:UITableViewCell {
var cellType: Int = 0 {
didSet {
let maskLayer = CAShapeLayer()
let cell: cellStyle = cellStyle(rawValue: cellType)!
switch cell {
case .Normal:
let normal = UIBezierPath(roundedRect: self.viewMain.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 0, height: 0))
maskLayer.path = normal.cgPath
self.viewMain.layer.mask = maskLayer
case .Rounded:
let rounded = UIBezierPath(roundedRect: self.viewMain.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10, height: 10))
maskLayer.path = rounded.cgPath
self.viewMain.layer.mask = maskLayer
}
}
}
}
In your ViewController->cellForRowAt --- Call below code on Main Queue
DispatchQueue.main.async {
if totalRows == index + 1 { //write your condition
cell.cellType = cellStyle.Rounded.rawValue
} else {
cell.cellType = cellStyle.Normal.rawValue
}
cell.layoutSubviews()
cell.layoutIfNeeded()
}
This worked for me:
view.clipsToBounds = true
view.layer.cornerRadius = value
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
Based on my experience, this is what works for me to get the dynamic-sized layers work properly.
//Inside the cell or the view class
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
if layer === self.layer {
//Do any dynamic-layer update here
myViewToRound.layer.cornerRadius = myViewToRound.bounds.width/2
}
}
here, replace myViewToRound with the UIView subclass that you want to be rounded inside your cell/view

How to use UIView in UIViewController ios

I make UIView like this. but I want to use this in UIViewController
What should I do?
import UIKit
class CardView: UIView {
#IBInspectable var cornerRadius: CGFloat = 2
#IBInspectable var shadowOffsetWidth: Int = 0
#IBInspectable var shadowOffsetHeight: Int = 3
#IBInspectable var shadowColor: UIColor? = UIColor.black
#IBInspectable var shadowOpacity: Float = 0.5
override func layoutSubviews() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
}
}
If you want to add it in your viewController you have several options:
Add it directly in storyboard (best option):
drag a view in viewController via inspector/object library on the bottom right side in xCode
UIView object screenshot
add constraints, then tap on the view in
Constraints screenshot
and pick your custom class in identity inspector/custom class field
You can also use a view loaded from xib
Google different way to load from xib.
You can add it in code. It's a little bit harder:
Create a lazy property:
lazy var cardView: CardView = {
let cardView = CardView(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
cardView.backgroundColor = .gray
cardView.layer.cornerRadius = 16.0
return cardView
}()
Add custom view in viewDidLoad for example:
override func viewDidLoad() {
super.viewDidLoad()
/* Adding subview to the VC */
view.addSubview(cardView)
}
Then add constraints. You can center it vertically/horizontally and then set custom height/width. It depends on what you want...
Check this out:
Swift | Adding constraints programmatically
I advise you to read Apple's documentation rather then just copy pasting code.
Should definitely read "documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html" if you still haven't.
Add a new uiview to your storyboard or xib file and set its class as CardView as shown

UIButton Round Corner not working properly on iPhone 5

this is by UI extension method
extension UIView {
func roundCorners(corners:UIRectCorner, radiusWidth: CGFloat,radiusHeight: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radiusWidth/2, height: radiusHeight/2))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}}
by this extension method i want to make my buttons with round corners with this code on viewdidload
btnRideNow.roundCorners(corners: [.topRight], radiusWidth: btnRideNow.frame.width,radiusHeight: btnRideNow.frame.height )
btnRideLater.roundCorners(corners: [.topLeft], radiusWidth: btnRideLater.frame.width,radiusHeight: btnRideLater.frame.height )
but on iPhone 5 i am getting this result
ScreenShot
you can see left button wouldn't render properly but in iPhone 6 this work properly Why?
It's all a matter of timing.
If you call roundCorners too soon, e.g. in viewDidLoad, the button's frame and bounds may not yet have been finalized. But your roundCorners depends on the bounds, so if you add the mask and the button is then resized as a result of layout, you will naturally get the wrong result.
If you want round corners you can simple do:
view.layer.cornerRadius
and if you want a border you can do
view.layer.borderWidth
and color
view.layer.borderColor
Here's a simple subclass that works with IB. You should be able to easily adapt it to your needs:
#IBDesignable
public class Button: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
Now if you wish to simply code things, you were given most of the answer. For a complete circle:
view.layer.cornerRadius = view.frame.height / 2
You can use width though, if it's square. But like #Matt says (he's very good), be careful to do this after you have the frame/bounds properly set!

Animating CALayer's shadowPath property

I am aware that a CALayer's shadowPath is only animatable using explicit animations, however I still cannot get this to work. I suspect that I am not passing the toValue properly - as I understand this has to be an id, yet the property takes a CGPathRef. Storing this in a UIBezierPath does not seem to work. I am using the following code to test:
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:#"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = [UIBezierPath bezierPathWithRect:CGRectMake(-10.0, -10.0, 50.0, 50.0)];
[self.view.layer addAnimation:theAnimation forKey:#"animateShadowPath"];
(I am using minus values so as to ensure the shadow extends beyond a view that lies on top of it... the layer's masksToBounds property is set to NO).
How is animation of the shadowPath achieved?
UPDATE
Problem nearly solved. Unfortunately, the main problem was a somewhat careless error...
The mistake I made was to add the animation to the root layer of the view controller, rather than the layer I had dedicated to the shadow. Also, #pe8ter was correct in that the toValue needs to be a CGPathRef cast to id (obviously when I had tried this before I still had no animation due to the wrong layer mistake). The animation works with the following code:
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:#"shadowPath"];
theAnimation.duration = 3.0;
theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:myRect].CGPath;
[controller.shadowLayer addAnimation:theAnimation forKey:#"shadowPath"];
I appreciate this was difficult to spot from the sample code I provided. Hopefully it can still be of use to people in a similar situation though.
However, when I try and add the line
controller.shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:myRect].CGPath;
the animation stops working, and the shadow just jumps to the final position instantly. Docs say to add the animation with the same key as the property being changed so as to override the implicit animation created when setting the value of the property, however shadowPath can't generate implicit animations... so how do I get the new property to stay after the animation?
Firstly, you did not set the animation's fromValue.
Secondly, you're correct: toValue accepts a CGPathRef, except it needs to be cast to id. Do something like this:
theAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:newRect].CGPath;
You'll also need to set the shadowPath property of the layer explicitly if you want the change to remain after animation.
let cornerRadious = 10.0
//
let shadowPathFrom = UIBezierPath(roundedRect: rect1, cornerRadius: cornerRadious)
let shadowPathTo = UIBezierPath(roundedRect: rect2, cornerRadius: cornerRadious)
//
layer.masksToBounds = false
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowOpacity = 0.6
//
let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowAnimation.fromValue = shadowPathFrom.CGPath
shadowAnimation.toValue = shadowPathTo.CGPath
shadowAnimation.duration = 0.4
shadowAnimation.autoreverses = true
shadowAnimation.removedOnCompletion = true
shadowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
layer.addAnimation(shadowAnimation, forKey: "shadowAnimation")
I've met same problem for shadow animation and find solution for persisting shadow after end of animation. You need to set final path to your shadow layer before you start animation. Reverting to initial shadow happens because CoreAnimation does not update properties of original layer, it creates copy of this layer and display animation on this copy (check Neko1kat answer for more details). After animation ended system removes this animated layer and return original layer, that has not updated path and your old shadow appears. Try this code:
let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowAnimation.fromValue = currentShadowPath
shadowAnimation.toValue = newShadowPath
shadowAnimation.duration = 3.0
shadowLayer.shadowPath = newShadowPath
shadowLayer.add(shadowAnimation, forKey: "shadowAnimation")
Although not directly answer this question, if you just needs a view can drop shadow, you can use my class directly.
/*
Shadow.swift
Copyright © 2018, 2020-2021 BB9z
https://github.com/BB9z/iOS-Project-Template
The MIT License
https://opensource.org/licenses/MIT
*/
/**
A view drops shadow.
*/
#IBDesignable
class ShadowView: UIView {
#IBInspectable var shadowOffset: CGPoint = CGPoint(x: 0, y: 8) {
didSet { needsUpdateStyle = true }
}
#IBInspectable var shadowBlur: CGFloat = 10 {
didSet { needsUpdateStyle = true }
}
#IBInspectable var shadowSpread: CGFloat = 0 {
didSet { needsUpdateStyle = true }
}
/// Set nil can disable shadow
#IBInspectable var shadowColor: UIColor? = UIColor.black.withAlphaComponent(0.3) {
didSet { needsUpdateStyle = true }
}
#IBInspectable var cornerRadius: CGFloat {
get { layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
private var needsUpdateStyle = false {
didSet {
guard needsUpdateStyle, !oldValue else { return }
DispatchQueue.main.async { [self] in
if needsUpdateStyle { updateLayerStyle() }
}
}
}
private func updateLayerStyle() {
needsUpdateStyle = false
if let color = shadowColor {
Shadow(view: self, offset: shadowOffset, blur: shadowBlur, spread: shadowSpread, color: color, cornerRadius: cornerRadius)
} else {
layer.shadowColor = nil
layer.shadowPath = nil
layer.shadowOpacity = 0
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
updateLayerStyle()
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
lastLayerSize = layer.bounds.size
if shadowColor != nil, layer.shadowOpacity == 0 {
updateLayerStyle()
}
}
private var lastLayerSize = CGSize.zero {
didSet {
if oldValue == lastLayerSize { return }
guard shadowColor != nil else { return }
updateShadowPathWithAnimationFixes(bonuds: layer.bounds)
}
}
// We needs some additional step to achieve smooth result when view resizing
private func updateShadowPathWithAnimationFixes(bonuds: CGRect) {
let rect = bonuds.insetBy(dx: shadowSpread, dy: shadowSpread)
let newShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
if let resizeAnimation = layer.animation(forKey: "bounds.size") {
let key = #keyPath(CALayer.shadowPath)
let shadowAnimation = CABasicAnimation(keyPath: key)
shadowAnimation.duration = resizeAnimation.duration
shadowAnimation.timingFunction = resizeAnimation.timingFunction
shadowAnimation.fromValue = layer.shadowPath
shadowAnimation.toValue = newShadowPath
layer.add(shadowAnimation, forKey: key)
}
layer.shadowPath = newShadowPath
}
}
/**
Make shadow with the same effect as Sketch app.
*/
func Shadow(view: UIView?, offset: CGPoint, blur: CGFloat, spread: CGFloat, color: UIColor, cornerRadius: CGFloat = 0) { // swiftlint:disable:this identifier_name
guard let layer = view?.layer else {
return
}
layer.shadowColor = color.cgColor
layer.shadowOffset = CGSize(width: offset.x, height: offset.y)
layer.shadowRadius = blur
layer.shadowOpacity = 1
layer.cornerRadius = cornerRadius
let rect = layer.bounds.insetBy(dx: spread, dy: spread)
layer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
}
via https://github.com/BB9z/iOS-Project-Template/blob/master/App/General/Effect/Shadow.swift

Resources