Create a custom box with an arrow underneath using swift programatic ui - ios

I am a junior swiftui developer, i am working on a project that require me to use UIKit in some of its part.
i want to achieve a view similar to the attached image which is an example of Waze Advertisment box:
after checking MapBox API and looking at some of the example i learned that if i want to achieve this i should use swift programatic ui which i am not that familiar with, in the example they have provided the following view:
and the way they have created it was as follow:
final class AdvertismentAnnotationView: UIView {
weak var delegate: AdvertismentAnnotationViewDelegate?
//Passing this model to get the data and display it
var advertismentModel: AdvertismentModel
var selected: Bool = false {
didSet {
selectButton.setTitle(selected ? "Deselect" : "Select", for: .normal)
}
}
var title: String? {
get { centerLabel.text }
set { centerLabel.text = newValue }
}
lazy var centerLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 10)
label.numberOfLines = 0
return label
}()
lazy var closeButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("X", for: .normal)
return button
}()
lazy var selectButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = #colorLiteral(red: 0, green: 0.4784313725, blue: 0.9882352941, alpha: 1)
button.layer.cornerRadius = 8
button.clipsToBounds = true
button.setTitle("Select", for: .normal)
return button
}()
//MARK: This is what i have added
//and image that will later fetch the image from advertisment.imageURL
lazy var adImage: UIImage = {
return UIImage()
}()
//MARK: Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .green
closeButton.addTarget(self, action: #selector(closePressed(sender:)), for: .touchUpInside)
selectButton.addTarget(self, action: #selector(selectPressed(sender:)), for: .touchUpInside)
[centerLabel, closeButton, selectButton].forEach { item in
item.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(item)
}
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 4),
closeButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
centerLabel.topAnchor.constraint(equalTo: topAnchor, constant: 4),
centerLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
centerLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 4),
selectButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
selectButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
selectButton.leftAnchor.constraint(equalTo: leftAnchor, constant: 4)
])
}
}
what i want to achieve is a white rounded rectangle with an arrow underneath in its center and an image inside the box.
I have tried using swiftui in this uikit however i failed many time.
if this was a regular swiftui i would have created a ZStack with a rounded rectangle and a custom shape as of the arrow.
and using kingfisher to fetch the image.

Related

Weird horizontal shrinking animation when hiding UIButton with Configuration in UIStackView

I'm facing this weird animation issues when hiding UIButton in a StackView using the new iOS 15 Configuration. See playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private weak var contentStackView: UIStackView!
override func viewDidLoad() {
view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
view.backgroundColor = .white
let contentStackView = UIStackView()
contentStackView.spacing = 8
contentStackView.axis = .vertical
for _ in 1...2 {
contentStackView.addArrangedSubview(makeConfigurationButton())
}
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addAction(buttonAction, for: .primaryActionTriggered)
view.addSubview(contentStackView)
view.addSubview(button)
contentStackView.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
self.contentStackView = contentStackView
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
toggleElement.isHidden.toggle()
toggleElement.alpha = toggleElement.isHidden ? 0 : 1
self?.contentStackView.layoutIfNeeded()
}
}
}
private func makeSystemButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("System Button", for: .normal)
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
return button
}
}
PlaygroundPage.current.liveView = MyViewController()
Which results in this animation:
But I want the animation to look like this, where the button only shrinks vertically:
Which you can replicate in the playground by just swapping contentStackView.addArrangedSubview(makeConfigurationButton()) for contentStackView.addArrangedSubview(makeSystemButton()).
I guess this has something to do with the stack view alignment, setting it to center gives me the desired animation, but then the buttons don't fill the stack view width anymore and setting the width through AutoLayout results in the same animation again... Also, having just one system button in the stack view results in the same weird animation, but why does it behave differently for two system buttons? What would be a good solution for this problem?
As you've seen, the built-in show/hide animation with UIStackView can be quirky (lots of other quirks when you really get into it).
It appears that, when using a button with UIButton.Configuration, the button's width changes from the width assigned by the stack view to its intrinsic width as the animation occurs.
We can get around that by giving the button an explicit height constraint -- but, what if we want to use the intrinsic height (which may not be known in advance)?
Instead of setting the constraint, set the button's Content Compression Resistance Priority::
button.configuration = config
// add this line
button.setContentCompressionResistancePriority(.required, for: .vertical)
return button
And we no longer get the horizontal sizing:
As you will notice, though, the button doesn't "squeeze" vertically... it gets "pushed up" outside the stack view's bounds.
We can avoid that by setting .clipsToBounds = true on the stack view:
If this effect is satisfactory, we're all set.
However, as we can see, the button is still not getting "squeezed." If that is the visual effect we want, we can use a custom "self-stylized" button instead of a Configuration button:
Of course, there is very little visual difference - and looking closely the button's text is not squeezing. If we really, really, really want that to happen, we need to animate a transform instead of using the stack view's default animation.
And... if we are taking advantage of some of the other conveniences with Configurations, using a self-stylized UIButton might not be an option.
If you want to play with the differences, here's some sample code:
class ViewController : UIViewController {
var btnStacks: [UIStackView] = []
override func viewDidLoad() {
view.backgroundColor = .systemYellow
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.spacing = 12
for i in 1...3 {
let cv = UIView()
cv.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
let label = UILabel()
label.backgroundColor = .yellow
label.font = .systemFont(ofSize: 15, weight: .light)
let st = UIStackView()
st.axis = .vertical
st.spacing = 8
if i == 1 {
label.text = "Original Configuration Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeOrigConfigurationButton())
}
}
if i == 2 {
label.text = "Resist Compression Configuration Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeConfigurationButton())
}
}
if i == 3 {
label.text = "Custom Buttons"
for _ in 1...2 {
st.addArrangedSubview(makeCustomButton())
}
}
st.translatesAutoresizingMaskIntoConstraints = false
cv.addSubview(st)
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: 28.0),
st.topAnchor.constraint(equalTo: cv.topAnchor),
st.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
st.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
cv.heightAnchor.constraint(equalToConstant: 100.0),
])
btnStacks.append(st)
outerStack.addArrangedSubview(label)
outerStack.addArrangedSubview(cv)
outerStack.setCustomSpacing(2.0, after: label)
}
// a horizontal stack view to hold a label and UISwitch
let ctbStack = UIStackView()
ctbStack.axis = .horizontal
ctbStack.spacing = 8
let label = UILabel()
label.text = "Clips to Bounds"
let ctbSwitch = UISwitch()
ctbSwitch.addTarget(self, action: #selector(switchChanged(_:)), for: .valueChanged)
ctbStack.addArrangedSubview(label)
ctbStack.addArrangedSubview(ctbSwitch)
// put the label/switch stack in a view so we can center it
let ctbView = UIView()
ctbStack.translatesAutoresizingMaskIntoConstraints = false
ctbView.addSubview(ctbStack)
// button to toggle isHidden/alpha on the first
// button in each stack view
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.backgroundColor = .white
button.addAction(buttonAction, for: .primaryActionTriggered)
outerStack.addArrangedSubview(ctbView)
outerStack.addArrangedSubview(button)
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
ctbStack.topAnchor.constraint(equalTo: ctbView.topAnchor),
ctbStack.bottomAnchor.constraint(equalTo: ctbView.bottomAnchor),
ctbStack.centerXAnchor.constraint(equalTo: ctbView.centerXAnchor),
])
}
#objc func switchChanged(_ sender: UISwitch) {
btnStacks.forEach { v in
v.clipsToBounds = sender.isOn
}
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0) {
guard let self = self else { return }
self.btnStacks.forEach { st in
st.arrangedSubviews[0].isHidden.toggle()
st.arrangedSubviews[0].alpha = st.arrangedSubviews[0].isHidden ? 0 : 1
}
}
}
}
private func makeOrigConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
config.title = "Configuration Button"
button.configuration = config
// add this line
button.setContentCompressionResistancePriority(.required, for: .vertical)
return button
}
private func makeCustomButton() -> UIButton {
let button = UIButton()
button.setTitle("Custom Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 6
return button
}
}
Looks like this:
Edit
Quick example of another "quirk" when it comes to hiding a stack view's arranged subview (excess code in here, but I stripped down the above example):
class MyViewController : UIViewController {
var btnStacks: [UIStackView] = []
override func viewDidLoad() {
view.backgroundColor = .systemYellow
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.spacing = 12
let cv = UIView()
cv.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
let label = UILabel()
label.backgroundColor = .yellow
label.font = .systemFont(ofSize: 15, weight: .light)
let st = UIStackView()
st.axis = .vertical
st.spacing = 8
let colors: [UIColor] = [
.cyan, .green, .yellow, .orange, .white
]
label.text = "Labels"
for j in 0..<colors.count {
let v = UILabel()
v.text = "Label"
v.textAlignment = .center
v.backgroundColor = colors[j]
if j == 2 {
v.text = "Height Constraint = 80.0"
v.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
}
st.addArrangedSubview(v)
}
st.translatesAutoresizingMaskIntoConstraints = false
cv.addSubview(st)
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: 28.0),
st.topAnchor.constraint(equalTo: cv.topAnchor),
st.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
st.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
cv.heightAnchor.constraint(equalToConstant: 300.0),
])
btnStacks.append(st)
outerStack.addArrangedSubview(label)
outerStack.addArrangedSubview(cv)
outerStack.setCustomSpacing(2.0, after: label)
// button to toggle isHidden/alpha on the first
// button in each stack view
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.backgroundColor = .white
button.addAction(buttonAction, for: .primaryActionTriggered)
outerStack.addArrangedSubview(button)
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0) {
guard let self = self else { return }
self.btnStacks.forEach { st in
st.arrangedSubviews[2].isHidden.toggle()
}
}
}
}
}
When this is run and the "Toggle" button is tapped, it will be painfully obvious what's "not-quite-right."
You should add height constraint to buttons and update this constraint while animating. I edit your code just as below.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private weak var contentStackView: UIStackView!
override func viewDidLoad() {
view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
view.backgroundColor = .white
let contentStackView = UIStackView()
contentStackView.spacing = 8
contentStackView.axis = .vertical
for _ in 1...2 {
contentStackView.addArrangedSubview(makeConfigurationButton())
}
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addAction(buttonAction, for: .primaryActionTriggered)
view.addSubview(contentStackView)
view.addSubview(button)
contentStackView.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
self.contentStackView = contentStackView
}
private var buttonAction: UIAction {
UIAction { [weak self] _ in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
toggleElement.isHidden.toggle()
toggleElement.alpha = toggleElement.isHidden ? 0 : 1
toggleElement.heightAnchor.constraint(equalToConstant: toggleElement.isHidden ? 0 : 50)
self?.contentStackView.layoutIfNeeded()
}
}
}
private func makeSystemButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("System Button", for: .normal)
return button
}
private func makeConfigurationButton() -> UIButton {
let button = UIButton()
var config = UIButton.Configuration.filled()
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50)
])
config.title = "Configuration Button"
button.configuration = config
return button
}
}
PlaygroundPage.current.liveView = MyViewController()

Updating UITextField.text when hitting button - swift

So I define my UITextField, and call it in my viewDidLoad function. From there I also call a UIButton that when pressed it reads the value of the text field as 'print(myTextField.text!). If I change the value in the text field again and hit the button, the value of myTextField.text! is still the same as before. Here is my text field and here is my button, im not sure how to implement an update call. My question is how can I read the updated value and why doesn't it update in the first place?
let myTextField: UITextField = {
let UTextFieldDesign = UITextField()
UTextFieldDesign.textColor = .black
return UTextFieldDesign
}()
let loginButton: UIButton = {
let LButton = UIButton(type: .system)
LButton.layer.cornerRadius = 25.0
LButton.setTitleColor(.white, for: .normal)
LButton.setTitle(NSLocalizedString("Check Text", comment: ""), for: .normal)
LButton.titleLabel?.font = .systemFont(ofSize: 20)
LButton.backgroundColor = LoginButtonColor
LButton.addTarget(self, action: #selector(loginAction), for: .touchUpInside)
return LButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTap()
setupMyTextField() //this just adds the constraints
setupLoginButton()
#objc func loginAction() {
print(myTextField.text!)
}
fileprivate func setupMyTextField() {
view.addSubview(myTextField)
myTextField.translatesAutoresizingMaskIntoConstraints = false
myTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
myTextField.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
myTextField.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
}
fileprivate func setupLoginButton() {
view.addSubview(loginButton)
loginButton.anchors(top: myTextField.bottomAnchor, topPad: 15, bottom: nil, bottomPad: 0,
left: view.leftAnchor, leftPad: 20, right: view.rightAnchor,
rightPad: -20, height: 50, width: 0)
loginButton.translatesAutoresizingMaskIntoConstraints = false
}

Constraints for two UIStackView - one under the other

I working on "Hangman" app. On the top I want image, in middle textfield, and bottom couple UIStackView row for letters. Now I try to add constraints for only two UIStackView one under the other, but one is always behind other ie. not see.
This is image where you can se only second stack view (named: stacklView2), and the first i cant see. Why? What I doing wrong?
And this is code:
import UIKit
class ViewController: UIViewController {
var imageView: UIImageView!
var answerTextfield: UITextField!
override func loadView() {
view = UIView()
view.backgroundColor = .white
imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.layer.borderWidth = 1
view.addSubview(imageView)
answerTextfield = UITextField()
answerTextfield.translatesAutoresizingMaskIntoConstraints = false
answerTextfield.textAlignment = .center
answerTextfield.isUserInteractionEnabled = false
answerTextfield.layer.borderWidth = 1
view.addSubview(answerTextfield)
let button1 = UIButton()
button1.backgroundColor = .red
let button2 = UIButton()
button2.backgroundColor = .blue
let stackView1 = UIStackView(arrangedSubviews: [button1, button2])
stackView1.translatesAutoresizingMaskIntoConstraints = false
stackView1.distribution = .fillEqually
stackView1.axis = .horizontal
view.addSubview(stackView1)
let stackView2 = UIStackView(arrangedSubviews: [button2, button1])
stackView2.translatesAutoresizingMaskIntoConstraints = false
stackView2.distribution = .fillEqually
stackView2.axis = .horizontal
view.addSubview(stackView2)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 5),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
answerTextfield.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20),
answerTextfield.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor, multiplier: 0.5),
answerTextfield.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView1.topAnchor.constraint(equalTo: answerTextfield.bottomAnchor, constant: 20),
stackView1.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor, multiplier: 1.0),
stackView1.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor),
//stackView1.bottomAnchor.constraint(equalTo: stackView2.layoutMarginsGuide.topAnchor, constant: -5),
stackView2.topAnchor.constraint(equalTo: stackView1.bottomAnchor, constant: 20),
stackView2.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor, multiplier: 1.0),
stackView2.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor),
stackView2.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -5),
])
}
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: "Hangman-0")
answerTextfield.text = "T E X T F I E L D"
setupNavigationBar()
}
func setupNavigationBar() {
let hintButton = UIButton(type: .infoLight)
hintButton.addTarget(self, action: #selector(hintTapped), for: .touchUpInside)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: hintButton)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(newGameTapped))
}
#objc func hintTapped() {
print("Hint button tapped!")
}
#objc func newGameTapped() {
print("New Game button tapped!")
}
}
You use same objects button1 and button2 in both stacks
let stackView1 = UIStackView(arrangedSubviews: [button1, button2])
let stackView2 = UIStackView(arrangedSubviews: [button2, button1])
so it's added to stackView2 only you need to create 2 other different ones as a single element can be added to only 1 view

Animating a view with AutoLayout but it changes without actually animating

I have a custom view subclass that I will provide all the code of for clarity. I have highlighted the relevant parts below.
Note: I know how to animate views using AutoLayout. The problem is not writing the animation code. The problem is that it updates the view but doesn't actually animate anything. It just jumps to the new size.
class ExpandingButtonView: UIView {
let titleLabel: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.textColor = .white
l.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .vertical)
l.setContentHuggingPriority(UILayoutPriorityRequired, for: .vertical)
return l
}()
let buttonStack: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.spacing = 8
s.isLayoutMarginsRelativeArrangement = true
s.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
return s
}()
var collapsed: Bool = true {
didSet {
animatedCollapsedState()
}
}
lazy var collapsedConstraint: NSLayoutConstraint = {
return self.bottomAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 10)
}()
lazy var expandedConstraint: NSLayoutConstraint = {
return self.bottomAnchor.constraint(equalTo: self.buttonStack.bottomAnchor)
}()
init(title: String, color: UIColor, buttonTitles: [String]) {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
layer.cornerRadius = 8
clipsToBounds = true
backgroundColor = color
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(toggleCollapsed))
tapGestureRecognizer.numberOfTapsRequired = 1
tapGestureRecognizer.numberOfTouchesRequired = 1
addGestureRecognizer(tapGestureRecognizer)
titleLabel.text = title
addSubview(titleLabel)
addSubview(buttonStack)
buttonTitles.forEach {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor(white: 1.0, alpha: 0.5)
button.setTitle($0, for: .normal)
button.tintColor = .white
button.layer.cornerRadius = 4
button.clipsToBounds = true
button.titleLabel?.font = .boldSystemFont(ofSize: 17)
button.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .vertical)
buttonStack.addArrangedSubview(button)
}
NSLayoutConstraint.activate([
collapsedConstraint,
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 10),
titleLabel.bottomAnchor.constraint(equalTo: buttonStack.topAnchor),
buttonStack.leadingAnchor.constraint(equalTo: leadingAnchor),
buttonStack.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func toggleCollapsed() {
collapsed = !collapsed
}
func animatedCollapsedState() {
UIView.animate(withDuration: 1) {
self.collapsedConstraint.isActive = self.collapsed
self.expandedConstraint.isActive = !self.collapsed
self.layoutIfNeeded()
}
}
}
It has two states...
Collapsed...
Expanded...
When you tap it the tapGestureRecognizer toggles the collapsed value which triggers the didSet which then animates the UI.
The animating function is...
func animatedCollapsedState() {
UIView.animate(withDuration: 1) {
self.collapsedConstraint.isActive = self.collapsed
self.expandedConstraint.isActive = !self.collapsed
self.layoutIfNeeded()
}
}
However... it is not animating. It just jumps to the new size without actually animating.
I have removed another part of the view for the question. There is also a background image view that fades in/out during the UI change. That DOES animate. So I'm not quite sure what's going on here?
I have also tried moving the constraint updates out of the animation block and also tried running layoutIfNeeded() before updating them.
In all cases it does the same thing jumping to the new size.
You have to call layoutIfNeeded() on the view's superview.
func animatedCollapsedState() {
self.collapsedConstraint.isActive = self.collapsed
self.expandedConstraint.isActive = !self.collapsed
UIView.animate(withDuration: 1) {
self.superview?.layoutIfNeeded()
}
}

Proper File structure in MVC, while making UI programmatically

I was wondering how to store in proper way Views, and all elements in iOS Projects, when I'm creating UI programmatically.
I have for ex. RegisterButton function, and this ViewController file.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(loginRegisterButton)
setupLoginRegisterButon()
}
let loginRegisterButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red: 120, green: 80, blue: 195)
button.setTitle("Register", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.white, for: .normal)
return button
}()
func setupLoginRegisterButon() {
loginRegisterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterButton.topAnchor.constraint(equalTo: inputsContrainerView.bottomAnchor, constant: 12).isActive = true
loginRegisterButton.widthAnchor.constraint(equalTo: inputsContrainerView.widthAnchor).isActive = true
loginRegisterButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
I should create separate file in "Views", but how to do it in proper, and how to call function that creates all objects in proper way.
Thanks!

Resources