Archid
Archid

Reputation: 407

How to position Layers in swift programmatically?

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)

Upvotes: 0

Views: 3326

Answers (3)

Archid
Archid

Reputation: 407

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
}

Upvotes: 0

matt
matt

Reputation: 535306

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)

Upvotes: 3

Josh Homann
Josh Homann

Reputation: 16327

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.

Upvotes: 1

Related Questions