0

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.

view 1 view 2

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:

  1. error: IB Designables: Failed to render and update auto layout status for ViewController (BYZ-38-t0r): The agent crashed
  2. error: IB Designables: Failed to update auto layout status: The agent crashed
sabi
  • 423
  • 2
  • 8
  • You can't. Since your constraints are coded and IB is a **design time** tool, it has no way to process *layoutSubviews()*. –  Feb 21 '17 at 15:25
  • thanks @dfd , can you explain why it goes fine after making an update frame or changing buttons number or spacing? – sabi Feb 21 '17 at 15:41
  • I may have been misreading something then, unless update frames somehow touches your code. One more thing to try... put a *getter* in it and see what happens. If that works, let me know. –  Feb 21 '17 at 16:40
  • @dfd edited as you suggested, but there were errors, as you can see. Any other suggestion? – sabi Feb 21 '17 at 17:08
  • I don't think you want the setupButtons() in the getter. That probably will get rid of those errors. –  Feb 21 '17 at 17:17
  • I'm of no help. First off, get rid of the getter. Also, I can't stand UIStackViews sometimes. :-) I took a look at your project, and I see what you see. I tried a few things and all I have noticed are (1) If you update frames the constraint warning goes away but the issue remains, (2) If you reopen the project the constraint warning appears, and (3) If you open the project with 4 buttons... do *not* update frames... add a fifth button... the constraint warning states the current value is 0,0! I have no idea what's happening. –  Feb 21 '17 at 17:43
  • @dfd thanks anyway for your effort! – sabi Feb 21 '17 at 17:45

0 Answers0