I have a custom implementation of a checkbox / radio button in Swift.
The class is annotated as @IBDesignable
. I implement layoutSubviews
and prepareForInterfaceBuilder
.
This implementation is distributed in a pod that is used in several applications. I know that Cocoapods has caused and is still causing some inconsistencies in the IB behavior, but I don't believe that it is part of the issue here.
Everything is fine when I run the application. However, when I use the interface builder, the box is always rendered on the top left of the parent view instead of being in its own view.
How can I fix this? Is my implementation of layoutSubviews
and prepareForInterfaceBuilder
relevant?
EDIT: I made a sample project here: https://github.com/csarkis/TestIBDesignablesPod/
Just run pod install
and open the project.
Here is a simplified rendition of the designable view:
class RadioButton: UIControl {
/// Radio button radius
private var radioButtonSize: CGFloat = 20
/// A Boolean value that determines the off/on state of the radio button. Default is on
@IBInspectable public var isOn: Bool = true { ... }
...
// MARK: Initialisers
/// :nodoc:
public override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
/// :nodoc:
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
/// Setup the view initially
private func setupViews() {
self.subviews.forEach { $0.removeFromSuperview() }
self.translatesAutoresizingMaskIntoConstraints = false
self.autoresizesSubviews = false
self.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.widthAnchor.constraint(equalToConstant: 40).isActive = true
prepareForInterfaceBuilder()
setNeedsDisplay()
}
/// Main color of the radio button element
private var color: UIColor { ... }
// MARK: View management
// MARK: Custom drawings
/// Width of the unchecked radio circle
private var borderWidth: CGFloat = 2
/// :nodoc:
public override func draw(_ rect: CGRect) {
super.draw(rect)
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(self.color.cgColor)
context.setFillColor(self.color.cgColor)
context.setLineWidth(self.borderWidth)
drawRadio(in: rect, for: context)
}
private func drawRadio(in rect: CGRect, for context: CGContext) {
// Radio button
// Draw inside the box, considering the border width
let newRect = rect.insetBy(dx: (rect.width - radioButtonSize + borderWidth) / 2,
dy: (rect.height - radioButtonSize + borderWidth) / 2)
// Draw the outlined circle
let borderCircle = UIBezierPath(ovalIn: newRect)
borderCircle.stroke()
context.addPath(borderCircle.cgPath)
context.strokePath()
context.fillPath()
...
}
/// :nodoc:
public override func layoutSubviews() {
super.layoutSubviews()
self.setNeedsDisplay()
}
/// :nodoc:
public override var intrinsicContentSize: CGSize {
CGSize(width: 40, height: 40)
}
/// :nodoc:
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setNeedsDisplay()
}
...
}
My observation on this simple pod is that the behavior is very inconsistant (a lot of crashes and random errors). Here, the checkbox is first rendered on the top left and moves to its correct position as soon as we modify any attribute. The class is RadioButton.swift.