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