Reputation: 269
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
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