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
Related
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.
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.
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
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)
How can I fix this issue?
The image is set with constraints in the storyboard; the other circle is set with these lines of code:
let centerY = profileImage.center.y+20+(self.navigationController?.navigationBar.frame.size.height)!
let centerX = profileImage.center.x
let center = CGPoint(x: centerX, y: centerY)
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: (profileImage.frame.width/2)+2, startAngle: -CGFloat.pi-CGFloat.pi/2, endAngle: CGFloat.pi/2, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = Functions.hexStringToUIColor(hex: "#3859B9").cgColor
trackLayer.lineWidth = 3
trackLayer.lineCap = kCALineCapRound
trackLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(trackLayer)
On iPhone 7 (the one on the right) seems to be right.
Using fixed values for navigation's or status' bar height will lead to such errors. You should position your layer according to the image view. For that you can simply assign the frame of the image view to your layer:
trackLayer.frame = profileImage.frame
Since the layout may change for several reasons (e.g. device rotation), you should do this in viewDidLayoutSubviews of your view controller.
Since the frame of the layer is now smaller than in your example, you should create the center with:
let center = CGPoint(x: profileImage.bounds.midX, y: profileImage.bounds.midY)
override func viewDidLayoutSubviews() {
let centerY = profileImage.center.y+20+(self.navigationController?.navigationBar.frame.size.height)!
let centerX = profileImage.center.x
let center = CGPoint(x: centerX, y: centerY)
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: (profileImage.frame.width/2)+2, startAngle: -CGFloat.pi-CGFloat.pi/2, endAngle: CGFloat.pi/2, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = Functions.hexStringToUIColor(hex: "#3859B9").cgColor
trackLayer.lineWidth = 3
trackLayer.lineCap = kCALineCapRound
trackLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(trackLayer)
}
OR in write the code in viewDidAppear without delay
This would fix the issue.
Add the layer to profileImage.layer.
trackLayer.frame = profileImage.bounds
Full code should be like this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let center = CGPoint(x: profileImage.bounds.midX,
y: profileImage.bounds.midY)
let trackLayer = CAShapeLayer()
trackLayer.frame = profileImage.frame
let circularPath = UIBezierPath(arcCenter: center,
radius: (profileImage.frame.width / 2) + 2,
startAngle: -CGFloat.pi - CGFloat.pi / 2,
endAngle: CGFloat.pi / 2, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = Functions.hexStringToUIColor(hex: "#3859B9").cgColor
trackLayer.lineWidth = 3
trackLayer.lineCap = kCALineCapRound
trackLayer.fillColor = UIColor.clear.cgColor
profileImage.layer.addSublayer(trackLayer)
}
Using interface builder:
Drag UIView and drop above the imageview
Change view custom class to CirculerProgressView
Set center X and Y constraints with imageview center
Set height and width constraints(greater then imageView height & width)
#IBDesignable class CirculerProgressView: UIView {
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setupLayer()
}
private func setupLayer() {
let trackLayer = layer as! CAShapeLayer
let center = CGPoint(x: frame.width/2, y: frame.height/2)
let circularPath = UIBezierPath(arcCenter: center, radius: frame.width/2, startAngle: -CGFloat.pi-CGFloat.pi/2, endAngle: CGFloat.pi/2, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.blue.cgColor
trackLayer.lineWidth = 3
trackLayer.lineCap = kCALineCapRound
trackLayer.fillColor = UIColor.clear.cgColor
}}