I'm working on a Radio Button View, and already uploaded a test project on github at https://github.com/paulicelli/RadioButton
This is basically a subclass of a UIStackView, with a IBInspectable number of buttons to create some radio buttons.
Working on the device, but I always see a misplacement of the buttons when I open Interface Builder, until I change the number of buttons or the spacing. You can see the problem in view1, then the correct view in view2 attached.
QUESTION: How can I tell Interface builder to properly position the buttons as soon as I open it?
here is the code:
import UIKit
// ROUNDBUTTON
final class RoundButton: UIButton {
var borderWidth: CGFloat = 2.0
var primaryColor: UIColor = UIColor.blue
var primaryColorWithAlpha: UIColor {
return primaryColor.withAlphaComponent(0.25)
}
override public var buttonType: UIButtonType {
return .custom
}
override public func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = bounds.size.width * 0.5
self.clipsToBounds = true
setupColors()
}
func setupColors() {
switch self.state {
case UIControlState.normal:
super.backgroundColor = .white
self.setTitleColor(primaryColor, for: state)
self.layer.borderColor = primaryColor.cgColor
self.layer.borderWidth = borderWidth
default:
super.backgroundColor = primaryColorWithAlpha
self.setTitleColor(.white, for: state)
}
}
}
// END OF ROUNDBUTTON
// PROTOCOL
@objc protocol RadioButtonsViewDelegate {
@objc optional func didSelectButton(_ aButton: RoundButton?)
}
// END OF PROTOCOL
// RADIOBUTTONSVIEW
@IBDesignable
class RadioButtonsView: UIStackView {
var shouldLetDeselect = true
var buttons = [RoundButton]()
weak var delegate : RadioButtonsViewDelegate? = nil
private(set) weak var currentSelectedButton: RoundButton? = nil
@IBInspectable
var number: Int = 2 {
didSet {
number = max(number, 2)
setupButtons()
}
}
func setupButtons() {
if !buttons.isEmpty {
buttons.forEach { self.removeArrangedSubview($0) }
buttons.removeAll()
}
for i in 0..<self.number {
let button = RoundButton()
button.setTitle("\(i)", for: .normal)
button.addTarget(self, action: #selector(pressed(_:)),
for: UIControlEvents.touchUpInside)
buttons.append(button)
}
}
func pressed(_ sender: RoundButton) {
if (sender.isSelected) {
if shouldLetDeselect {
sender.isSelected = false
currentSelectedButton = nil
}
}else {
buttons.forEach { $0.isSelected = false }
sender.isSelected = true
currentSelectedButton = sender
}
delegate?.didSelectButton?(currentSelectedButton)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupButtons()
}
override var intrinsicContentSize: CGSize {
return CGSize(width: self.bounds.width,
height: UIViewNoIntrinsicMetric)
}
override func layoutSubviews() {
self.translatesAutoresizingMaskIntoConstraints = false
buttons.forEach { self.addArrangedSubview($0) }
let constraints: [NSLayoutConstraint] = {
var constraints = [NSLayoutConstraint]()
constraints.append(NSLayoutConstraint(item: buttons[0],
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: self,
attribute: NSLayoutAttribute.height,
multiplier: 1,
constant: 0)
)
for i in 1..<buttons.count {
constraints.append(NSLayoutConstraint(item: buttons[i],
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: buttons[i - 1],
attribute: NSLayoutAttribute.width,
multiplier: 1,
constant: 0)
)
}
return constraints
}()
self.addConstraints(constraints)
}
}
// END OF RADIOBUTTONSVIEW
EDIT: As requested by @dfd I also tried:
@IBInspectable
var number: Int {
get {
setupButtons()
return self.number
}
set {
self.number = max(newValue, 2)
setupButtons()
}
}
and I got 2 errors while loading Interface Builder:
- error: IB Designables: Failed to render and update auto layout status for ViewController (BYZ-38-t0r): The agent crashed
- error: IB Designables: Failed to update auto layout status: The agent crashed