0
votes

I am trying to position a circular progress bar but I am failing constantly. I guess I am not understanding the concept of frames and views and bounds. I found this post on stack overflow that shows me exactly how to construct a circular progress bar.

However, in the post, a circleView was created as UIView using CGRect(x: 100, y: 100, width: 100, height: 100)

In my case, manually setting the x and y coordinates is obv a big no. So the circleView has to be in the centre of it's parent view.

Here is my view hierarchy:

view -> view2ForHomeController -> middleView -> circleView

So everything is positioned using auto layout. Here is the problem:The circleView is adding properly and it positions itself at the x and y value I specify but how can I specify the x and y values of the center of middleView. I tried the following but the center values are returned as 0.0, 0.0.

self.view.addSubview(self.view2ForHomeController)
self.view2ForHomeController.fillSuperview()

let xPosition = self.view2ForHomeController.middleView.frame.midX 
let yPostion = self.view2ForHomeController.middleView.frame.midY
print(xPosition) // ------- PRINTS ZERO
print(yPostion)  // ------- PRINTS ZERO

let circle = UIView(frame: CGRect(x: xPosition, y: yPostion, width: 100, height: 100))
circle.backgroundColor = UIColor.green
self.view2ForHomeController.middleView.addSubview(circle)

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

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

let circlePath = UIBezierPath(ovalIn: rectFofOval)

progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.white.cgColor
progressCircle.fillColor = UIColor.clear.cgColor
progressCircle.lineWidth = 10.0
progressCircle.frame = self.view.bounds
progressCircle.lineCap = CAShapeLayerLineCap(rawValue: "round")

circle.layer.addSublayer(progressCircle)
circle.transform = circle.transform.rotated(by: CGFloat.pi/2)

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.1
animation.duration = 1
animation.repeatCount = MAXFLOAT
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)

progressCircle.add(animation, forKey: nil)
3

3 Answers

2
votes

Frame is in terms of superview/superlayer coordinates. If you are going to say

circle.layer.addSublayer(progressCircle)

then you must give progressCircle a frame in terms of the bounds of circle.layer.

As for centering, in general, to center a sublayer in its superlayer you say:

theSublayer.position = CGPoint(
    x:theSuperlayer.bounds.midX, 
    y:theSuperlayer.bounds.midY)

And to center a subview in its superview you say:

theSubview.center = CGPoint(
    x:theSuperview.bounds.midX, 
    y:theSuperview.bounds.midY)
1
votes

The bounds are the layer/view's coordinates in its local coordinate system. The frame is the layer/view's coordinates in its parents coordinate system. Since layers do not participate in auto layout, you should implement UIViewController.viewDidLayoutSubviews (or UIView.layoutSubLayers) and set the frame of the layer to the bounds of its super layer's view (the backing layer of a UIView essentially is the CoreGraphics version of that view).

This also means you need to recompute your path in they layout method. if that is expensive then draw your path in unit space and use the transform of the layer to scale it up to the view's dimensions instead.

0
votes

The solution is to first add the subviews and then create the entire circular progress view at

override func viewDidLayoutSubviews(){
     super.viewDidLayoutSubviews()
     // Add and position the circular progress bar and its layers here
}