1

I am trying to add a CAShapeLayer to a view. But when I add the customView to ViewCOntroller's View, the shapeLayer doesn't show up. Capturing hierarchy shows that the layer doesn't even get inserted to the View. Here is the code:

override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
...

override func didMoveToSuperview() {
        let diameter = min(self.frame.width, self.frame.height)
        let path = UIBezierPath(arcCenter: self.center, radius: diameter/2, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)
        let sLayer = CAShapeLayer()
        sLayer.frame = bounds
        sLayer.path = path.cgPath
        sLayer.fillMode = .backwards
        sLayer.fillColor = UIColor.blue.cgColor//bubbleColor?.cgColor
        layer.addSublayer(sLayer)
    }

And in ViewController:

override func viewDidLoad() {
        super.viewDidLoad()
        let node = Node(frame: CGRect(origin: self.view.center, size: CGSize(width: 250, height: 250)))
        self.view.addSubview(node)
...
}

I really do not understand what is happening. Please help!

  • Out of curiosity, why are you setting `layerClass`, but then adding a `CAShapeLayer`. So, you've got two completely different `CAShapeLayer` instances. Is that your intent? Why are you changing the base `layerClass`? – Rob Dec 27 '18 at 17:56
  • 1
    I was trying without it. And then got a reference here with it. I just added it and mistakenly copied over here. –  Dec 27 '18 at 19:18
  • OK. No worries. You can do rendition of this with `layerClass` if you want, but then we don’t add sublayer. It’s one or the other. – Rob Dec 27 '18 at 19:42
  • Yes, I understand. Thanks for solving the problem on both perspective. `Constraints` were playing a vital role here. –  Dec 28 '18 at 19:53

1 Answers1

1

I'd suggest:

  1. adding your sublayer in init (so it's added once and only once);
  2. configure its frame and path in layoutSubviews (so if it's resized, whether by constraints or some other mechanism, the layer will update accordingly);
  3. when adding a subview/sublayer whose dimensions is based upon the size of the current view, reference the view’s bounds (the coordinate system within the current view), not the frame nor center (both of which are in the coordinate system of the superview); and
  4. either use addSublayer approach or layerClass approach, but not both.

Thus, adding CAShapeLayer as sublayer:

@IBDesignable
class CircleView: UIView {
    @IBInspectable var bubbleColor: UIColor = .blue { didSet { shapeLayer.fillColor = bubbleColor.cgColor } }

    private lazy var shapeLayer: CAShapeLayer = {
        let sLayer = CAShapeLayer()
        sLayer.fillColor = bubbleColor.cgColor
        return sLayer
    }()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        shapeLayer.frame = bounds
        updatePath()
    }
}

// MARK: - Utility methods

private extension CircleView {
    func configure() {
        layer.addSublayer(shapeLayer)
    }

    func updatePath() {
        let diameter = min(bounds.width, bounds.height)
        let arcCenter = CGPoint(x: bounds.midX, y: bounds.midY)
        shapeLayer.path = UIBezierPath(arcCenter: arcCenter, radius: diameter / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
    }
}

Yielding:

enter image description here


Or, if you want to use layerClass approach:

@IBDesignable
class CircleView: UIView {
    @IBInspectable var bubbleColor: UIColor = .blue { didSet { shapeLayer.fillColor = bubbleColor.cgColor } }

    override class var layerClass: AnyClass { return CAShapeLayer.self }

    private var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer}

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updatePath()
    }
}

// MARK: - Utility methods

private extension CircleView {
    func configure() {
        shapeLayer.fillColor = bubbleColor.cgColor
    }

    func updatePath() {
        let diameter = min(bounds.width, bounds.height)
        let arcCenter = CGPoint(x: bounds.midX, y: bounds.midY)
        shapeLayer.path = UIBezierPath(arcCenter: arcCenter, radius: diameter / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044