How draw a circular progress bar with CAShapLayer and auto layout constraints - ios

I'm drawing a circular progress bar using CAShapeLayer, I can produce it only if I center it on my view. Now I want to constraint it to the trailing edge of my card view. Below is my code:
let center = cardView.center
//track layer
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.gray.cgColor
trackLayer.strokeEnd = 1
trackLayer.lineCap = kCALineCapRound
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 5
cardView.layer.addSublayer(trackLayer)
//progress Layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.yellow.cgColor
//NumberFormatter().number(from: percentageUsed) as! CGFloat
shapeLayer.strokeEnd = 0.6
shapeLayer.lineCap = kCALineCapRound
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 5
cardView.layer.addSublayer(shapeLayer)
Now I wish i could do something like
shapeLayer.leadingAnchor.constraint(equalTo: cardVeiw.leadingAnchor, constant: 20).isActive = true

Related

CAShapeLayer is added to the view, but not around my button

I'd like to add a circular CAShapeLayer around my button that I've implemented with Storyboard. I'm doing this because I want to add a clockwise circle and pulsing animation similar to these two videos: #1 and #2. I don't want a border as a shape is required. I've tried two approaches to and it is not going directly around my button.
I've tried it with viewDidLoad and viewDidLayoutSubviews. Both approaches put it in the top left corner. For brevity's sake, I only attached the viewDidLayoutSubviews below. The only change in viewDidLoad is the addition of self.view.layoutIfNeeded().
var wasCAShapeLayerAddedToButton = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !wasCAShapeLayerAddedToButton {
wasCAShapeLayerAddedToButton = true
startBiteButton.layer.cornerRadius = startBiteButton.frame.width / 2
addCAShapeLayerToButton()
}
}
func addCAShapeLayerToButton() {
let circularPath = UIBezierPath(arcCenter: startBiteButton.center, radius: startBiteButton.frame.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
let trackLayer = CAShapeLayer()
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 2
trackLayer.lineCap = CAShapeLayerLineCap.round
view.layer.addSublayer(trackLayer)
recordLine.path = circularPath.cgPath
recordLine.strokeColor = UIColor(red: 0.42, green: 0.39, blue: 1.00, alpha: 1.00).cgColor
recordLine.fillColor = UIColor.clear.cgColor
recordLine.lineWidth = 2
recordLine.lineCap = CAShapeLayerLineCap.round
recordLine.strokeEnd = 0
view.layer.addSublayer(recordLine)
pulsatingLayer = CAShapeLayer()
pulsatingLayer.path = circularPath.cgPath
pulsatingLayer.strokeColor = UIColor.clear.cgColor
pulsatingLayer.fillColor = UIColor.yellow.cgColor
pulsatingLayer.lineWidth = 2
pulsatingLayer.lineCap = CAShapeLayerLineCap.round
view.layer.addSublayer(pulsatingLayer)
animatePulsatingLayer()
}
Here is how it's adding the shape.

Positioning of CAShapeLayer

I'm currently trying to implement a CAShapeLayer. It works fine, but its position isn't working the way I want it to.
I want the circle to be displayed inside of the top selected UIView you see in the second image below. To do that, I tried to set arcCenter to circleView.center. But what's being displayed is the image below.
How it's being displayed:
I want it to be displayed in the center inside of this UIView:
My code:
func displayCircle() {
let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1)
let trackLayer = CAShapeLayer()
let center = circleView.center
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.groupTableViewBackground.cgColor
trackLayer.lineWidth = 20
trackLayer.fillColor = UIColor.clear.cgColor
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = 20
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.strokeEnd = 0
circleView.layer.addSublayer(trackLayer)
circleView.layer.addSublayer(shapeLayer)
}
Edit, UIView's constraints:
You can simply change your center position of circle view like below,
let center = CGPoint(x: circleView.frame.size.width/2, y: circleView.frame.size.height/2)
Hope this way may help you.

how to move position of shape layer (swift4)

My code right now uses a circular progress view and it places in dead center of the screen. I need this exact circular progress view to move about 150 pixels down the x axis.
import UIKit
class ViewController: UIViewController {
let shapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let center = view.center
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
view.layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 10
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 0
view.layer.addSublayer(shapeLayer)
}
You can try , you say down Y
let center = CGPoint(x:view.center.x,y:view.center.y + 150)
Or right x
let center = CGPoint(x:view.center.x + 150 ,y:view.center.y)

Custom circular progress view in awakeFromNib

I have created circle progress view using UIBezierPath and CAShapeLayer with following code.
let center = self.center
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
self.layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 10
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 0
self.layer.addSublayer(shapeLayer)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
If I put that code in my viewDidLoad it works fine. However, when I add UIView from storyboard and set its class to custom class then copy all codes to that custom UIView class and call it in awakeFromNib function it is not working what could be the reason? And how can I use above code with custom UIView class?
Orange view should be my custom view I have added leading, trailing, bottom and fixed height constraint it but it looks like above. Does not circle view need to place center of orange view?
Actually it works but it has wrong position. The position in this case is out of your view so you can't see it. It makes you think that your code doesn't work.
If you want to have circle in the center of your view. Put the code in awakeFromNib and calculate center your self, don't use self.center
override func awakeFromNib() {
let center = CGPoint.init(x: frame.width / 2, y: frame.height / 2)
let trackLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
self.layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 10
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 0
self.layer.addSublayer(shapeLayer)
}
Result

Circular progress in swift

I am trying to make a simple circular progress bar in swift. What I have failed to do so far is, the progress should start at the top of the circle (at the moment it is starting at the 90 degree point). I would also like to have a circular line under the progress line that should be thicker than the progress line and have a different color as well..
Can anyone put me in the right direction towards my wishes?
Here is my function:
func animateView() {
let circle = viewProgress // viewProgress is a UIView
var progressCircle = CAShapeLayer()
let circlePath = UIBezierPath(ovalInRect: circle.bounds)
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 5.0
circle.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.5
animation.duration = 1
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
progressCircle.addAnimation(animation, forKey: "ani")
}
Igor this is how it looks:
For Swift 4
Declare two CAShapeLayers. One for actual circle and another for progress Layer!
func createCircularPath() {
let circleLayer = CAShapeLayer()
let progressLayer = CAShapeLayer()
let center = self.view.center
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -.pi / 2, endAngle: 2 * .pi, clockwise: true)
circleLayer.path = circularPath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.lightGray.cgColor
circleLayer.lineCap = .round
circleLayer.lineWidth = 20.0 //for thicker circle compared to progressLayer
progressLayer.path = circularPath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = UIColor.blue.cgColor
progressLayer.lineCap = .round
progressLayer.lineWidth = 10.0
progressLayer.strokeEnd = 0
view.addSublayer(circleLayer)
view.addSublayer(progressLayer)
let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")
progressAnimation.toValue = 1
progressAnimation.fillMode = .forwards
progressAnimation.isRemovedOnCompletion = false
progressLayer.add(progressAnimation, forKey: "progressAnim")
}
Happy Coding!
Hope this answer helps :]

Resources