In my iOS project, I'm working with a UIStackView that contains multiple subviews. I want to dynamically flip or move up and down specific views within the stack view based on a condition. Specifically,
I'm attempting to manipulate the positions of two text fields named FIRST and SECOND. However, I'm encountering an issue where the flipping only works correctly on every second tap or click.
Here is my code for the same
import UIKit
import Combine
import SnapKit
class ViewController: UIViewController {
private var toggleSubject = PassthroughSubject<Bool, Never>()
private var toggle = false
private var store = Set<AnyCancellable>()
private lazy var topField: AppTextField = {
let view = AppTextField(title: "First")
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var bottomField: AppTextField = {
let view = AppTextField(title: "Second")
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var lineView: UIImageView = {
let view = UIImageView(frame: .zero)
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var stackView: UIStackView = {
let view = UIStackView(arrangedSubviews: [topField, lineView, bottomField, toggleButton])
view.axis = .vertical
view.spacing = 4.0
view.distribution = .fill
view.alignment = .fill
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var toggleButton: UIButton = {
let view = UIButton(frame: .zero)
view.setTitle("Toggle", for: .normal)
view.setTitleColor(.white, for: .normal)
view.setTitleColor(.systemYellow, for: .highlighted)
view.backgroundColor = .darkGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var selectedTopFieldLabel: UILabel = {
let view = UILabel(frame: .zero)
view.textAlignment = .center
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(stackView)
self.view.addSubview(selectedTopFieldLabel)
toggleSubject.send(toggle)
stackView.snp.makeConstraints { make in
make.leading.trailing.centerY.equalToSuperview().inset(16)
}
lineView.snp.makeConstraints { make in
make.height.equalTo(0.5)
}
selectedTopFieldLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(200)
}
toggleButton.addTarget(self, action: #selector(toggleButtonAction), for: .touchUpInside)
toggleSubject.sink { toggle in
print("---> Changed Toggle",toggle)
self.updateUI(toggle: toggle)
UIView.animate(withDuration: 0.25) {
self.stackView.layoutIfNeeded()
}
}.store(in: &store)
self.updateUI(toggle: toggle)
}
@objc func toggleButtonAction() {
toggle.toggle()
toggleSubject.send(toggle)
}
private func updateUI(toggle: Bool) {
let arrangedSubviews = self.stackView.arrangedSubviews
guard var first = arrangedSubviews[0] as? AppTextField,
var second = arrangedSubviews[2] as? AppTextField else {return}
stackView.arrangedSubviews.forEach({$0.removeFromSuperview()})
let changedViews = toggle ? [first, lineView, second, toggleButton] : [second, lineView, first , toggleButton]
guard let firstTextField = changedViews.first as? AppTextField,
let firstfieldPlaceholder = firstTextField.placeholder else {return}
self.selectedTopFieldLabel.text = "Selected On Top " + " -> " + firstfieldPlaceholder
changedViews.forEach { view in
self.stackView.addArrangedSubview(view)
}
// Update constraints
UIView.animate(withDuration: 0.25) {
self.stackView.setNeedsLayout()
self.stackView.layoutIfNeeded()
}
}
}
class AppTextField: UIView {
private lazy var inputField: UITextField = {
let textField = UITextField(frame: .zero)
textField.backgroundColor = .lightGray.withAlphaComponent(0.15)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
var title: String? {
get { return inputField.placeholder }
set { inputField.placeholder = newValue }
}
var placeholder: String? {
get { return inputField.placeholder }
set { inputField.placeholder = newValue }
}
var text: String? {
get { return inputField.text }
set { inputField.text = newValue }
}
init(title: String) {
super.init(frame: .zero)
self.title = title
setupViews()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
addSubview(inputField)
inputField.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.height.equalTo(48)
}
}
}
Thanks in advance.
The problem is your logic...
When you tap the button, you toggle toggle
between True and False
Then you call updateUI(toggle: toggle)
, at which point this code:
let changedViews = toggle ? [first, lineView, second, toggleButton] : [second, lineView, first , toggleButton]
Says:
toggle
is True
Which means your button taps are saying:
So, if your goal is to change the order on every button tap, you don't need the toggle
bool var at all. Change your UI update to this:
private func updateUI() {
let arrangedSubviews = self.stackView.arrangedSubviews
guard let first = arrangedSubviews[0] as? AppTextField,
let second = arrangedSubviews[2] as? AppTextField else {return}
let changedViews = [second, lineView, first , toggleButton]
guard let firstTextField = changedViews.first as? AppTextField,
let firstfieldPlaceholder = firstTextField.placeholder else {return}
self.selectedTopFieldLabel.text = "Selected On Top " + " -> " + firstfieldPlaceholder
changedViews.forEach { view in
self.stackView.addArrangedSubview(view)
}
// Update constraints
UIView.animate(withDuration: 0.25) {
self.stackView.setNeedsLayout()
self.stackView.layoutIfNeeded()
}
}
and call it without the bool:
self.updateUI()
Or, if your goal is to set the order based on a Bool var, you might change your code to this:
private func updateUI(firstOnTop: Bool) {
let changedViews = firstOnTop ? [self.topField, lineView, self.bottomField, toggleButton] : [self.bottomField, lineView, self.topField , toggleButton]
guard let firstTextField = changedViews.first as? AppTextField,
let firstfieldPlaceholder = firstTextField.placeholder else {return}
self.selectedTopFieldLabel.text = "Selected On Top " + " -> " + firstfieldPlaceholder
changedViews.forEach { view in
self.stackView.addArrangedSubview(view)
}
// Update constraints
UIView.animate(withDuration: 0.25) {
self.stackView.setNeedsLayout()
self.stackView.layoutIfNeeded()
}
}
and call it like this:
self.updateUI(firstOnTop: toggle)