Incorrect Position of Cashapelayer

Incorrect position of CAShapeLayer

Basically u have few problems:

  1. incorrect position of your layer - frame should be equal to bounds of parent layer in parentView
  2. you animation will play only once - if u want to make it infinite add something like animation.repeatCount = MAXFLOAT
  3. to make sure that all size correct - in layoutSubviews recreate layers, with removing previously created one
  4. UIBezierPath(arcCenter... will start path from rightSide, u can also use simpler variant like UIBezierPath(ovalInRect. With this in mind u also need to rotate layer for M_PI_2 if u want to make start of drawing in the very top of circle
  5. U should also take in mind that with line width 10, half of u'r circle path will be catted by clipSubviews of parentView. To prevent this use modified parentLayer rect
  6. U can also maybe want to improve visualization of lineCap, buy setting it to "round" style
  7. progressCircle.addAnimation(animation, forKey: "ani") key for anim not required if u dont want to get completion event from animation - i your case i see there is no delegate and no removeOnCompletion flag setted to false, so make assumption that u don`t actually need it

Keeping this all in mind code can be like :

let circle = UIView(frame: CGRectMake(100, 100, 100, 100)) // viewProgress is a UIView
circle.backgroundColor = UIColor.greenColor()
view.addSubview(circle)

var progressCircle = CAShapeLayer()
progressCircle.frame = view.bounds

let lineWidth:CGFloat = 10
let rectFofOval = CGRectMake(lineWidth / 2, lineWidth / 2, circle.bounds.width - lineWidth, circle.bounds.height - lineWidth)

let circlePath = UIBezierPath(ovalInRect: rectFofOval)

progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.whiteColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 10.0
progressCircle.frame = view.bounds
progressCircle.lineCap = "round"

circle.layer.addSublayer(progressCircle)
circle.transform = CGAffineTransformRotate(circle.transform, CGFloat(-M_PI_2))

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.1
animation.duration = 1
animation.repeatCount = MAXFLOAT
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

progressCircle.addAnimation(animation, forKey: nil)

and result actually like:

Sample Image

Note:
If u want to animate rotation of animated layer u should also add additional animation in to anim group

Wrong position of CAShapelayer in sublayers of UIView

As the answer says, simply change shape.frame to shape.bounds in your call to build().

Remember that the coordinates of your shape layer are relative to the containing view, so bounds needs to be used.

CAShapeLayer position incorrect

In your AnimationCircleView inside setup change

shapeLayer.frame = self.frame

to

shapeLayer.frame = self.bounds

This should ensure the gray colour is drawn inside the yellow rectangle.

Edit:

Similar to the answer above, changing

let circlePath = UIBezierPath(ovalIn: frame)

to

let circlePath = UIBezierPath(ovalIn: bounds)

will solve your problem and draw circle inside the gray area.

CAShapeLayer frame is calculated relative to the position of the view it is added on, soo setting it equal to the frame, adds the CAShapeLayer at a x and y position offset from the origin by the value of the x and y passed in self.frame.

How to position CAShapeLayer in a UIView

The problem is this line:

shapeLayer.position = CGPoint(x: drawingView.frame.midX, y: drawingView.frame.midY)

Everywhere you are saying drawingView.frame, say drawingView.bounds instead.

(Actually you should be saying drawingView.layer.bounds, not simply drawingView.bounds, but it happens that they are the same thing.)

However, there's also a second problem, which is that your shape layer has no size. Therefore its position is the same as its top left corner. For this reason you would be much better off centering it in its superlayer just by saying

shapeLayer.frame = drawingView.layer.bounds

You will then have the shape layer exactly occupying the drawing view. (You will be able to see that clearly if you give the shape layer a background color.) Now your problem will be that you want to make sure the drawing path itself is centered in the shape layer, but that is a different exercise.

adding CAlayer with frame displays at wrong position

The problem with placing code in a method other than viewDidAppear/viewDidLayoutSubviews is that the real frame is not yet rendered , so it should be like this but keep in mind to wrap it inside an once bool as this method is called multiple times

 -(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if(once)
{
once = No;
CALayer *featureLayer = [[CALayer alloc]init];
featureLayer.borderWidth=3;
featureLayer.backgroundColor=[[UIColor orangeColor]CGColor];
featureLayer.opacity = 0.5;
featureLayer.position = CGPointMake(0, 0);
[featureLayer setFrame:self.cardHolder.frame];
[self.videoPreviewLayer addSublayer:featureLayer]
}
}

Aligning CAShapeLayer center of my UIView

I suspect the cell size change after you have set the layer. The easiest place to fix this is in the cell's layoutSubviews(). Since your path is using absolute coordinates you need to update both the layer frame and the path.

override func layoutSubviews() {
super.layoutSubviews()

// You need to keep trackLayer around after you created it
let rectangle = UIBezierPath(rect: cell.calorieBurnUIView.bounds)
self.trackLayer.frame = cell.calorieBurnUIView.bounds
self.trackLayer.path = rectangle.cgPath
self.trackLayer.position = cell.calorieBurnUIView.center
}

Stroke of CAShapeLayer stops at wrong point?

Can anyone tell me what I'm missing here. Perhaps a problem with the start or end angle of my circular path?

Exactly. Think about your path (circularPath) which is assigned as the path of the shape layer. It goes from startAngle: -CGFloat.pi / 2 to endAngle: 2 * CGFloat.pi. That is more than just a complete circle. Do you see? It's a one-and-a-quarter circle!

That's why, above 0.78 (actually 0.8 or four-fifths), a change in the value of your strokeEnd appears to change nothing; you are just drawing over the start of the circle, redrawing a part of the circle that you already drew.

You probably meant

startAngle: -.pi / 2.0, endAngle: 3 * .pi / 2.0

(although what I would do is go from 0 to .pi*2 and apply a rotation transform so as to start the drawing at the top).


Another problem with your code, which could cause trouble down the line, is that you have forgotten to give either of your layers a frame. Thus they have no size.

How do I get the coordinates from CAShapeLayer

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


Related Topics



Leave a reply



Submit