Suneet Agrawal
Suneet Agrawal

Reputation: 269

draw(_ rect: CGRect) not getting called in custom view

I am trying to create a custom view by extending the class UIView which can show a circle at the centre of the custom view. In order to add custom drawing, I override draw(_ rect: CGRect) method as below.

    public override func draw(_ rect: CGRect) {
        super.draw(rect)
        let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
        let minLength = bounds.width <= bounds.height ? bounds.width : bounds.height
        let circlePath = UIBezierPath (arcCenter: center, radius: CGFloat(minLength  / 2), startAngle: 0, endAngle: CGFloat(Float(360).degreesToRadians) , clockwise: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = circleColor.cgColor
        layer.addSublayer(shapeLayer)
    }

But when I try to add the same custom component in storyboard and added a breakpoint in draw method, it was not getting called. To my curiosity, I added a breakpoint to init?(coder aDecoder: NSCoder) method which was getting called. Also when I try to override the method as below, it worked fine.

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

fileprivate func drawCircle() {
    let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
    let minLength = bounds.width <= bounds.height ? bounds.width : bounds.height
    let circlePath = UIBezierPath (arcCenter: center, radius: CGFloat(minLength  / 2), startAngle: 0, endAngle: CGFloat(Float(360).degreesToRadians) , clockwise: true)

    let shapeLayer = CAShapeLayer()
    shapeLayer.path = circlePath.cgPath
    shapeLayer.fillColor = circleColor.cgColor

    layer.addSublayer(shapeLayer)
}

The issue with approach is I have a custom property circleColor

@IBInspectable
public var circleColor : UIColor = UIColor.gray

which is not yet set when init method is called and it always draws a gray circle.

Please help to get me know the reason why draw method is not being called.

Thanks in advance

Upvotes: 2

Views: 2234

Answers (1)

Sulthan
Sulthan

Reputation: 130082

I am not sure where your root mistake lies but you should not override draw(:) just to add a layer. That would mean a new layer will be added again every time the component rerenders. That's not what you want.

You should also make sure the layer color is updated when you change the color property. To do that, just save the reference to the layer:

private let shapeLayer = CAShapeLayer()

@IBInspectable
public var circleColor : UIColor = UIColor.gray  {
   didSet {
      shapeLayer.fillColor = circleColor.cgColor
   }
}

public override init(frame: CGRect = .zero) {
    super.init(frame: frame)
    updateLayer()
    layer.addSublayer(shapeLayer)
}

required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    updateLayer()
    layer.addSublayer(shapeLayer)
}

fileprivate func updateLayer() {
    let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
    let minLength = min(bounds.width, bounds.height)
    let circlePath = UIBezierPath(arcCenter: center, radius: minLength / 2, startAngle: 0, endAngle: CGFloat(Float(360).degreesToRadians), clockwise: true)

    shapeLayer.path = circlePath.cgPath
    shapeLayer.fillColor = circleColor.cgColor
}

override func layoutSubviews() {
    super.layoutSubviews()
    // update layer position
    updateLayer()
}

If you really want to override draw(rect:), you don't need to create a layer or override initializers at all:

@IBInspectable
public var circleColor : UIColor = UIColor.gray  {
   didSet {
      setNeedsDisplay()
   }
}

public override func draw(_ rect: CGRect) {
    // no need to call super.draw(rect)

    let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
    let minLength = min(bounds.width, bounds.height)
    let circlePath = UIBezierPath(arcCenter: center, radius: minLength / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true)

    // the following methods use current CGContext
    circleColor.set()
    circlePath.fill()
}

Upvotes: 4

Related Questions