Reputation: 51
So I am trying to make a progress bar. So I have made circular path, but I want the dot to be at the end of the progress bar, but how do I get the position of the dot to be att the end of the current progress?
private func simpleShape() {
let width: CGFloat = 10
createCircle()
//make circle transparant in middle
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = UIColor.blue.cgColor
progressLayer.lineCap = CAShapeLayerLineCap.round
progressLayer.lineWidth = width
progressLayer.strokeStart = 0
progressLayer.strokeEnd = 0
//unfilled
backLayer.lineWidth = width
backLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1).cgColor
backLayer.strokeEnd = 1
self.layer.addSublayer(gradientLayer)
}
private func createCircle() {
//create circle
let circle = UIView(frame: bounds)
circle.layoutIfNeeded()
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius: CGFloat = circle.bounds.width / 2 * 0.83
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.475 * Double.pi), endAngle: CGFloat(1.525 * Double.pi), clockwise: true)
//add layers
progressLayer.path = circlePath.cgPath
backLayer.path = circlePath.cgPath
circle.layer.addSublayer(backLayer)
circle.layer.addSublayer(progressLayer)
addSubview(circle)
circle.layer.addSublayer(dotLayer)
}
let dotLayer = CAShapeLayer()
public func setProgress(_ progress: CGFloat) {
progressLayer.strokeEnd = CGFloat(progress)
if let progressEndpoint = progressLayer.path?.currentPoint {
dotLayer.position = progressEndpoint
}
}
This is what I'm getting
This is what I want
Upvotes: 1
Views: 950
Reputation: 14397
What you need is rotation Animation
let progressLayer = CAShapeLayer()
let backLayer = CAShapeLayer()
private func simpleShape() {
let width: CGFloat = 15
createCircle()
//make circle transparant in middle
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = #colorLiteral(red: 0.888897419, green: 0.5411034822, blue: 0.04008810222, alpha: 1)
progressLayer.lineCap = CAShapeLayerLineCap.round
progressLayer.lineWidth = width
progressLayer.strokeStart = 0
progressLayer.strokeEnd = 0
//unfilled
backLayer.lineWidth = width
backLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1)
backLayer.strokeEnd = 1
// self.layer.addSublayer(gradientLayer)
}
private func createCircle() {
//create circle
let circle = UIView(frame: bounds)
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius: CGFloat = circle.bounds.width / 2 * 0.83
let distance = circle.bounds.width / 2 * 0.17
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.475 * Double.pi), endAngle: CGFloat(1.525 * Double.pi), clockwise: true)
//add layers
progressLayer.path = circlePath.cgPath
backLayer.path = circlePath.cgPath
circle.layer.addSublayer(backLayer)
circle.layer.addSublayer(progressLayer)
addSubview(circle)
let circleCenter = CGPoint(x:centerPoint.x - distance,y:centerPoint.y - circleRadius )
let dotCircle = UIBezierPath()
dotCircle.addArc(withCenter:circleCenter, radius: 3, startAngle: CGFloat(-90).deg2rad(), endAngle: CGFloat(270).deg2rad(), clockwise: true)
dotLayer.path = dotCircle.cgPath
dotLayer.position = CGPoint(x:centerPoint.x,y:centerPoint.y )
dotLayer.strokeColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.6496753961)
dotLayer.lineWidth = 10
dotLayer.fillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
dotLayer.isHidden = true
circle.layer.addSublayer(dotLayer)
}
let dotLayer = CAShapeLayer()
public func setProgress(_ progress: CGFloat) {
print(progress)
// progressLayer.strokeEnd = progress
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.beginTime = CACurrentMediaTime() + 0.5;
animation.fromValue = 0
animation.toValue = progress
animation.duration = 2
animation.autoreverses = false
animation.repeatCount = .nan
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
progressLayer.add(animation, forKey: "line")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.dotLayer.isHidden = false
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
// rotateAnimation.beginTime = CACurrentMediaTime() + 0.5;
rotateAnimation.fromValue = (CGFloat( -90)).deg2rad()
rotateAnimation.toValue = (360*progress - 98).deg2rad()
rotateAnimation.duration = 2
rotateAnimation.fillMode = .forwards
rotateAnimation.isRemovedOnCompletion = false
self.dotLayer.add(rotateAnimation, forKey: nil)
}
}
Upvotes: 1
Reputation: 437552
You’re going to have to calculate it yourself. So figure out the angle from the start and end angles for your arcs:
let angle = (endAngle - startAngle) * progress + startAngle
And then use basic trigonometry to determine where that point falls:
let point = CGPoint(x: centerPoint.x + radius * cos(angle),
y: centerPoint.y + radius * sin(angle))
dotLayer.position = point
By the way, I’d suggest separating the adding of the sublayers (which is part of the initial configuration process) from the updating paths (which is part of the view layout process, which may be called again if the frame of the view changes, constraints are applied, etc). Thus, perhaps:
@IBDesignable
class ProgressView: UIView {
var progress: CGFloat = 0 { didSet { updateProgress() } }
private var centerPoint: CGPoint = .zero
private var radius: CGFloat = 0
private let startAngle: CGFloat = -0.475 * .pi
private let endAngle: CGFloat = 1.525 * .pi
private let lineWidth: CGFloat = 10
private lazy var progressLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineCap = .round
shapeLayer.lineWidth = lineWidth
shapeLayer.strokeStart = 0
shapeLayer.strokeEnd = progress
return shapeLayer
}()
private lazy var backLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = lineWidth
shapeLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1).cgColor
return shapeLayer
}()
private lazy var dotLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(arcCenter: .zero, radius: lineWidth / 2 * 1.75, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath
shapeLayer.fillColor = UIColor.white.withAlphaComponent(0.5).cgColor
return shapeLayer
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSublayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addSublayers()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePaths()
updateProgress()
}
}
private extension ProgressView {
func addSublayers() {
layer.addSublayer(backLayer)
layer.addSublayer(progressLayer)
layer.addSublayer(dotLayer)
}
func updatePaths() {
centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
radius = min(bounds.width, bounds.height) / 2 * 0.83
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
progressLayer.path = circlePath.cgPath
backLayer.path = circlePath.cgPath
}
func updateProgress() {
progressLayer.strokeEnd = progress
let angle = (endAngle - startAngle) * progress + startAngle
let point = CGPoint(x: centerPoint.x + radius * cos(angle),
y: centerPoint.y + radius * sin(angle))
dotLayer.position = point
}
}
Upvotes: 5