Exploring stackviews I've ran into a problem of incorrect representation if views inside of it. So, to make a long story short... I've made a custom checkbox:
class CheckBox: UIView, CheckBoxProtocol {
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var isChecked = true
lazy var checkmark: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
imageView.isHidden = false
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
@objc func toggle() {
self.isChecked.toggle()
self.checkmark.isHidden = !self.isChecked
}
In the Controller, when I add this view to the subviews it looks fairly normal and works as it should work (check-uncheck)
However when I add checkbox to the stackview it looses its visible frame and its functionality (does not check-uncheck) - you can see it on the screenshot
Here is the code from the ViewController:
class SettingsViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
self.view.addSubview(stackView)
}
override func viewDidLoad() {
super.viewDidLoad()
}
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 150, y: 150, width: 0, height: 0))
stackView.axis = .horizontal
stackView.spacing = 50
stackView.alignment = .fill
stackView.distribution = .fillEqually
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {stackView.addArrangedSubview($0)}
return stackView
}()
private let frame = CGRect(x: 0, y: 0, width: 30, height: 30)
lazy var redCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.red)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greenCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.green)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var blackCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.black)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greyCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.grey)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var brownCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.brown)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var yellowCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.yellow)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var purpleCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.purple)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var orangeCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.orange)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
CodePudding user response:
It's because we're working with the lazy property and its life cycle can be a little different. Let's set constraints after the view has loaded. What I would suggest to do:
For each checkbox, change the frame to zero:
lazy var orangeCheckbox: CheckBox = { let colorFactory = CardViewFactory() let color = colorFactory.getViewColor(modelColor: CardColor.orange) let checkbox = CheckBox(frame: .zero, color: colorFactory) checkbox.translatesAutoresizingMaskIntoConstraints = false return checkbox }()
Do the same to the stackView:
lazy var stackView: UIStackView = { let stackView = UIStackView(frame: .zero) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .horizontal stackView.spacing = 5 stackView.alignment = .fill stackView.distribution = .fillEqually [redCheckbox, greenCheckbox, blackCheckbox, greyCheckbox, brownCheckbox, yellowCheckbox, purpleCheckbox, orangeCheckbox].forEach {stackView.addArrangedSubview($0)} return stackView }()
Add some constraints on viewDidLoad:
override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(stackView) stackView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor, constant: -100).isActive = true stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 10).isActive = true stackView.heightAnchor.constraint(equalToConstant: 40).isActive = true [redCheckbox, greenCheckbox, blackCheckbox, greyCheckbox, brownCheckbox, yellowCheckbox, purpleCheckbox, orangeCheckbox].forEach { $0.heightAnchor.constraint(equalToConstant: 30).isActive = true $0.widthAnchor.constraint(equalToConstant: 30).isActive = true } }
What you can do to the image inside the checkBox to work fine:
translatesAutoresizingMaskIntoConstraints = false
lazy var checkmark: UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) imageView.translatesAutoresizingMaskIntoConstraints = false imageView.isHidden = false imageView.contentMode = .scaleAspectFit imageView.image = UIImage(systemName: "checkmark") return imageView }()
on your required init:
required init(frame: CGRect, color: UIColor) { super.init(frame: frame) self.layer.borderWidth = 5 self.layer.borderColor = color.cgColor self.addSubview(checkmark) checkmark.tintColor = color checkmark.topAnchor.constraint(equalTo: topAnchor).isActive = true checkmark.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true checkmark.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true checkmark.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle)) self.addGestureRecognizer(gesture) setNeedsDisplay() }