You can do this quite easily, without the need to keep a reference to bt4
and bt5
:
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
// if true replace bt4 in stackView with bt5
// must have 5 buttons in the stack view
guard stackView.arrangedSubviews.count == 5 else { return }
stackView.arrangedSubviews[3].isHidden = val
stackView.arrangedSubviews[4].isHidden = !val
}
If you really want to keep a separate reference to the buttons, and add-to/remove-from the stack view, your can do this:
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
// if true replace bt4 in stackView with bt5
let btnToShow: UIButton = val ? bt5 : bt4
// we only want to replace the button if it's not already there
guard let lastButton = stackView.arrangedSubviews.last as? UIButton,
lastButton != btnToShow
else { return }
lastButton.removeFromSuperview()
stackView.addArrangedSubview(btnToShow)
}
Here are complete examples...
First, using .isHidden
approach:
class StackViewController: UIViewController {
lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.axis = .horizontal
sv.distribution = .fillEqually
sv.alignment = .fill
sv.spacing = 12
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
configureStackView()
}
func configureStackView() {
for i in 1...5 {
let b = UIButton()
b.setTitle("\(i)", for: [])
b.backgroundColor = .red
stackView.addArrangedSubview(b)
}
// place stackView at bottom of scene
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
])
// add a couple "set val" buttons
let btnSV: UIStackView = {
let sv = UIStackView()
sv.axis = .horizontal
sv.distribution = .fillEqually
sv.alignment = .fill
sv.spacing = 12
return sv
}()
["True", "False"].forEach { t in
let b = UIButton()
b.setTitle(t, for: [])
b.backgroundColor = .blue
b.addTarget(self, action: #selector(setTrueFalse(_:)), for: .touchUpInside)
btnSV.addArrangedSubview(b)
}
btnSV.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnSV)
NSLayoutConstraint.activate([
btnSV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
btnSV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
btnSV.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// start with button "5" hidden
swapLastButtonInStackViewWithNewButton(false)
}
@objc func setTrueFalse(_ sender: UIButton) {
guard let t = sender.currentTitle else { return }
swapLastButtonInStackViewWithNewButton(t == "True")
}
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
// if true replace bt4 in stackView with bt5
// must have 5 buttons in the stack view
guard stackView.arrangedSubviews.count == 5 else { return }
stackView.arrangedSubviews[3].isHidden = val
stackView.arrangedSubviews[4].isHidden = !val
}
}
or, using a reference to bt4
and bt5
and adding/removing them:
class StackViewController: UIViewController {
lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.axis = .horizontal
sv.distribution = .fillEqually
sv.alignment = .fill
sv.spacing = 12
return sv
}()
var bt4: UIButton!
var bt5: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
configureStackView()
}
func configureStackView() {
for i in 1...5 {
let b = UIButton()
b.setTitle("\(i)", for: [])
b.backgroundColor = .red
stackView.addArrangedSubview(b)
}
// place stackView at bottom of scene
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
])
// add a couple "set val" buttons
let btnSV: UIStackView = {
let sv = UIStackView()
sv.axis = .horizontal
sv.distribution = .fillEqually
sv.alignment = .fill
sv.spacing = 12
return sv
}()
["True", "False"].forEach { t in
let b = UIButton()
b.setTitle(t, for: [])
b.backgroundColor = .blue
b.addTarget(self, action: #selector(setTrueFalse(_:)), for: .touchUpInside)
btnSV.addArrangedSubview(b)
}
btnSV.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnSV)
NSLayoutConstraint.activate([
btnSV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
btnSV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
btnSV.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this would go at the end of configureStackView(), but
// we'll put it here to keep the changes obvious
// references to btn4 and btn5
guard stackView.arrangedSubviews.count == 5,
let b4 = stackView.arrangedSubviews[3] as? UIButton,
let b5 = stackView.arrangedSubviews[4] as? UIButton
else {
fatalError("Bad setup - stackView does not have 5 buttons!")
}
bt4 = b4
bt5 = b5
// start with button "5" hidden
swapLastButtonInStackViewWithNewButton(false)
}
@objc func setTrueFalse(_ sender: UIButton) {
guard let t = sender.currentTitle else { return }
swapLastButtonInStackViewWithNewButton(t == "True")
}
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
// if true replace bt4 in stackView with bt5
let btnToShow: UIButton = val ? bt5 : bt4
// we only want to replace the button if it's not already there
guard let lastButton = stackView.arrangedSubviews.last as? UIButton,
lastButton != btnToShow
else { return }
lastButton.removeFromSuperview()
stackView.addArrangedSubview(btnToShow)
}
}
Edit
The above code might seem a little overly complicated -- but I think that's more related to all of the setup and "extra" checks.
As a more straight-forward answer...
As long as you have setup your stack view and have valid references to bt4
and bt5
, all you need to do is this:
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
// if true replace bt4 in stackView with bt5
if val {
bt4.removeFromSuperview()
stackView.addArrangedSubview(bt5)
} else {
bt5.removeFromSuperview()
stackView.addArrangedSubview(bt4)
}
}
That will avoid the animation issues.