CALayer Auto Layout

889 views ios
3

I am trying to create 2 layers that stack on top of each other and are inside of a custom view class.

the drawing for the layers is working great but I just can't get them to adjust their location based on the location of the view which has been set using auto layouts where the view is created in code (no IB).

I have tried changing the frame of the layer with a call to layoutSubviews here is the code for the try.

import UIKit

class ProgressView: UIView {

    let displayLayer:  CAShapeLayer = {
        let display = CAShapeLayer()
        display.strokeColor = UIColor.red.cgColor
        display.lineWidth = 8.0
        display.lineCap = CAShapeLayerLineCap.round
        display.fillColor = UIColor.clear.cgColor
        display.strokeEnd = 0.5

        return display
    }()

    let trackLayer: CAShapeLayer = {
        let track = CAShapeLayer()

        track.strokeColor = UIColor.lightGray.cgColor
        track.lineWidth = 6.0
        track.lineCap = CAShapeLayerLineCap.round
        track.fillColor = UIColor.clear.cgColor

        return track
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        let progressCircle = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)

        displayLayer.path = progressCircle.cgPath
        trackLayer.path = progressCircle.cgPath

        layer.addSublayer(trackLayer)
        layer.addSublayer(displayLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        displayLayer.frame = bounds
        trackLayer.frame = bounds
    }
}

After some research I also tried the KVO approach and used the following code:

import UIKit

class ProgressView: UIView {
    let displayLayer:  CAShapeLayer = {
        let display = CAShapeLayer()

        display.strokeColor = UIColor.red.cgColor
        display.lineWidth = 8.0
        display.lineCap = CAShapeLayerLineCap.round
        display.fillColor = UIColor.clear.cgColor
        display.strokeEnd = 0.5

        return display
    }()

    let trackLayer: CAShapeLayer = {
        let track = CAShapeLayer()

        track.strokeColor = UIColor.lightGray.cgColor
        track.lineWidth = 6.0
        track.lineCap = CAShapeLayerLineCap.round
        track.fillColor = UIColor.clear.cgColor

        return track
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        let progressCircle = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)

        displayLayer.path = progressCircle.cgPath
        trackLayer.path = progressCircle.cgPath

        layer.addSublayer(trackLayer)
        layer.addSublayer(displayLayer)

        self.addObserver(self, forKeyPath: #keyPath(ProgressView.bounds), options: .new, context: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == #keyPath(ProgressView.bounds) {
            displayLayer.frame = self.bounds
            trackLayer.frame = self.bounds
        }
    }
}

With each example I keep getting the same result with the following being the display. Mind that purple box is the ProgressView where I am expecting the two layers to be inside of.

I keep looking at this and thinking there is something simple that I am missing or doing wrong and I just can't see it.

I have put print statement in both observeValue as well as layoutSubviews to make sure they were being called and that was working.

Here is what I am seeing: enter image description here

Thanks in advance.

answered question

1 Answer

1

You need to provide the correct arcCenter as below and no need to set a frame for the shapLayer.

override func layoutSubviews() {
    super.layoutSubviews()

    let center = CGPoint(x: bounds.midX, y: bounds.midY)
    let progressCircle = UIBezierPath(arcCenter: center, radius: 50, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)

    displayLayer.path = progressCircle.cgPath
    trackLayer.path = progressCircle.cgPath
}

Result

enter image description here

Note: I used the first ProgressView class without KVO.

posted this

Have an answer?

JD

Please login first before posting an answer.