Reputation: 2212
I have a chart like below:
I have a problem with animation and shadows.
i draw a gradient with animation but from the beginning i have the shadow and mask layer which i don't want, i want the shadow animating with the gradient.
the current chart with animation is like below.
i dont want the user see the shadow and mask layer from the beginning.
here is my code:
import Foundation
import UIKit
@IBDesignable class CircularProgressView: UIView {
@IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
@IBInspectable var gradientStartColor: UIColor = UIColor.green
@IBInspectable var gradientEndColor: UIColor = UIColor.yellow
@IBInspectable var arcWidth: CGFloat = 20
override init(frame: CGRect) {
super.init(frame: frame)
circularProgressView_init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circularProgressView_init()
}
fileprivate func circularProgressView_init() {
let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
self.addConstraint(viewHeight)
}
override func prepareForInterfaceBuilder() {
circularProgressView_init()
}
override func draw(_ rect: CGRect) {
let width = self.bounds.width
let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
let progressEndAngle: CGFloat = CGFloat.pi / 2
//fill circular
let circlePath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: 0,
endAngle: 360,
clockwise: true)
circlePath.lineWidth = arcWidth
containerCircleColor.setStroke()
circlePath.stroke()
//MARK: ProgressPath
let progressPath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: progressStartAngle,
endAngle: progressEndAngle,
clockwise: true)
progressPath.lineWidth = arcWidth
progressPath.lineCapStyle = .round
//MARK: Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x:1, y:1)
gradientLayer.frame = self.bounds
//MARK: Animation
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.duration = 2
anim.fillMode = kCAFillModeForwards
anim.fromValue = 0
anim.toValue = 1
//MARK: Mask Layer
let maskLayer = CAShapeLayer()
maskLayer.path = progressPath.cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = arcWidth
maskLayer.lineCap = kCALineCapRound
gradientLayer.mask = maskLayer
self.layer.insertSublayer(gradientLayer, at: 0)
let context = UIGraphicsGetCurrentContext()
let shadow = UIColor.lightGray
let shadowOffset = CGSize(width: 3.1, height: 3.1)
let shadowBlurRadius: CGFloat = 5
context!.saveGState()
context!.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: (shadow as UIColor).cgColor)
progressPath.stroke()
context?.restoreGState()
maskLayer.add(anim, forKey: nil)
gradientLayer.add(anim, forKey: nil)
}
}
Is it possible at all? If it is not, how can i at least hide the shadow and mask and show it after animation ends?
Upvotes: 1
Views: 1150
Reputation: 2212
Well, i put the correct answer here, maybe some one need it in the future.
import Foundation
import UIKit
@IBDesignable class CircularProgressView: UIView {
@IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
@IBInspectable var gradientStartColor: UIColor = UIColor.yellow
@IBInspectable var gradientEndColor: UIColor = UIColor.red
@IBInspectable var arcWidth: CGFloat = 20
@IBInspectable var progressPercent: CGFloat = 50
override init(frame: CGRect) {
super.init(frame: frame)
circularProgressView_init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circularProgressView_init()
}
fileprivate func circularProgressView_init() {
let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
self.addConstraint(viewHeight)
}
override func prepareForInterfaceBuilder() {
circularProgressView_init()
}
override func draw(_ rect: CGRect) {
let width = self.bounds.width
let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
let progressEndAngle: CGFloat = ConvertToTrigonometry.shared.trigonimetryCordinate(percentage: progressPercent) //CGFloat.pi / 2
//fill circular
let circlePath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: 0,
endAngle: 360,
clockwise: true)
circlePath.lineWidth = arcWidth
containerCircleColor.setStroke()
circlePath.stroke()
//MARK: ProgressPath
let progressPath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: progressStartAngle,
endAngle: progressEndAngle,
clockwise: true)
progressPath.lineWidth = arcWidth
progressPath.lineCapStyle = .round
//MARK: Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x:1, y:1)
gradientLayer.frame = self.bounds
//MARK: Mask Layer
let maskLayer = CAShapeLayer()
maskLayer.path = progressPath.cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = arcWidth
maskLayer.lineCap = kCALineCapRound
maskLayer.masksToBounds = false
gradientLayer.mask = maskLayer
//MARK: Shadow
let shadowLayer = CAShapeLayer()
shadowLayer.frame = bounds
shadowLayer.backgroundColor = UIColor.gray.cgColor
layer.addSublayer(shadowLayer)
let maskShadowLayer = CAShapeLayer()
maskShadowLayer.path = progressPath.cgPath
maskShadowLayer.fillColor = UIColor.clear.cgColor
maskShadowLayer.backgroundColor = UIColor.black.cgColor
maskShadowLayer.strokeColor = UIColor.black.cgColor
maskShadowLayer.lineWidth = arcWidth
maskShadowLayer.lineCap = kCALineCapRound
maskShadowLayer.masksToBounds = false
maskShadowLayer.shadowColor = UIColor.black.cgColor
maskShadowLayer.shadowOpacity = 0.5
maskShadowLayer.shadowOffset = CGSize(width: 3.1, height: 3.1)
shadowLayer.mask = maskShadowLayer
//MARK: Animation
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.duration = 2
anim.fillMode = kCAFillModeForwards
anim.fromValue = 0
anim.toValue = 1
maskShadowLayer.add(anim, forKey: nil)
maskLayer.add(anim, forKey: nil)
gradientLayer.add(anim, forKey: nil)
layer.addSublayer(gradientLayer)
}
}
And also a helper class which i use for trigonometry conversion:
import Foundation
import UIKit
class ConvertToTrigonometry {
static let shared = ConvertToTrigonometry()
func trigonimetryCordinate(percentage: CGFloat) -> CGFloat {
let pi = CGFloat.pi
let trigonometryRatio = (percentage * 360) / 100 // How much you want to move forward in axis.
let endPointDegree = (3 * pi / 2) + ((trigonometryRatio * 2 / 360) * pi) // End point on axis based on your trigonometryRatio and the start point which is 3pi/2
return endPointDegree
}
}
this solution draw an arc with animating gradient and shadow. you can find the complete project in my Github.
Upvotes: 1
Reputation: 625
You should create another layer to give the shadow.
1 - first you have to create a UIView as a shadowLayer
2 - then mask this shadowLayer
with the path and layer same as the maskLayer
(in your code) you have already created. e.g: shadowMaskLayer
3 - add the shadow properties to this new shadowMaskLayer
4 - then add the shadowLayer
to the original UIView CircularProgressView
5 - also add the animation you already have to this shadowLayer
to animate the shadow with the whole circle.
Don't hesitate to ask questions ;)
Upvotes: 1