Swift - constraint animation - incorrect subviews animating - ios

I have extended UIView:
class A: UIView {
private var _height: NSLayoutConstraint!
private var _centerLabel: UILabel = {
let lb = UILabel()
lb.text = "Some text"
lb.adjustsFontSizeToFitWidth = true
lb.textAlignment = .center
lb.translatesAutoresizingMaskIntoConstraints = false
return lb
}()
func open() {
self._height.constant = 50
self.superview?.layoutIfNeeded()
}
func close() {
UIView.animate(withDuration: 1, delay: 1, options: .curveLinear, animations: {
self._height.constant = 0
self.superview?.layoutIfNeeded()
}, completion: nil)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(_centerLabel)
_height = self.heightAnchor.constraint(equalToConstant: 0)
NSLayoutConstraint.activate([
_centerLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
_centerLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
_centerLabel.topAnchor.constraint(lessThanOrEqualTo: self.topAnchor, constant: 16),
_centerLabel.bottomAnchor.constraint(greaterThanOrEqualTo: self.bottomAnchor, constant: -16),
_height
])
}
}
MainViewController:
class MainViewController: UIViewController {
#IBOutlet weak var a: A!
override func viewDidLoad() {
super.viewDidLoad()
  }
#IBAction func open(_ sender: Any) {
a.open()
}
#IBAction func close(_ sender: Any) {
a.close()
}
}
If I call close(), instance of UIView A animates correctly, all other constraint attached to it as well. However, content of A (_centerLabel) disappears immediatelly, its height is not animated at all. Why?

Related

UIView animating its own height

I have made a simple example of a UIView inside a UIViewController, so that when I tap on the inner view, it increases and decreases its height. That works fine, but I can't make the height transition animate.
Here is the view controller, followed by the view:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let theView = MyView()
theView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(theView)
NSLayoutConstraint.activate([
theView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 50.0),
theView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant:0.0),
theView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),
theView.heightAnchor.constraint(equalToConstant: 925)
])
}
}
class MyView: UIView {
fileprivate var closedChanger = NSLayoutConstraint()
fileprivate var isOpen = false
init() {
super.init(frame: CGRect.zero)
setupView()
}
fileprivate func setupView() {
self.backgroundColor = .gray
self.translatesAutoresizingMaskIntoConstraints = true
closedChanger = self.heightAnchor.constraint(equalToConstant: 150.0)
NSLayoutConstraint.activate([
closedChanger // start off with it shorter
])
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:))))
}
#objc func handleTap(sender: UITapGestureRecognizer) {
UIView.animate(withDuration: 1.0, animations: {
self.closedChanger.constant = self.isOpen ? self.closedChanger.constant - 25 : self.closedChanger.constant + 25
// self.layoutIfNeeded()
})
isOpen.toggle()
}
}
The layoutIfNeeded doesn't affect it. Is a UIView not allowed to animate its own height change?
Your are kind of right when you say that a view can't animate itself. The problem is that you need to animate the change to the containing view, since that the is the view hierarchy that is changing.
You need to make the change to the constraint outside of the animation block, and then call layoutIfNeeded on the superview in the animation block.
#objc func handleTap(sender: UITapGestureRecognizer) {
self.closedChanger.constant = self.isOpen ? self.closedChanger.constant - 25 : self.closedChanger.constant + 25
UIView.animate(withDuration: 1.0, animations: {
self.superview?.layoutIfNeeded()
})
isOpen.toggle()
}

Updating multiple UI Elements inside Timer.scheduledTimer doesn't work

I am trying to make an image "move down" every second while also showing a time counter. I have a scheduledTimer where I change the image's Y center and update the timer. The timer label updates but the image does not move. Strangely I can get the image to move down if I comment the line where I update the timer's UILabel. So apparently I can't update both.
I have tried adding the Timer to RunLoop, using a DispatchQueue and creating a Timer that takes a selector but nothing works.
class ViewController: UIViewController {
let starImage = UIImageView(image: UIImage(systemName: "star.fill"))
let timerLabel = UILabel()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
setupViews()
gameEngine()
}
private func setupViews() {
view.addSubview(starImage)
starImage.tintColor = .systemYellow
starImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(timerLabel)
timerLabel.tintColor = .white
timerLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
starImage.heightAnchor.constraint(equalToConstant: 100),
starImage.widthAnchor.constraint(equalToConstant: 100),
starImage.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
starImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),
timerLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20),
timerLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
// PROBLEM HERE
private func gameEngine() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] _ in
self?.counter += 1
self?.timerLabel.text = "Timer: \(self!.counter)"
self?.starImage.center.y += 10
}
}
}
I got it to work by using UIView.animate as Rob suggested it in the comments.
I replaced the line where I updated the Y value with this:
UIView.animate(withDuration: 1, animations: {
[weak self] in
self?.starImage.transform = CGAffineTransform(translationX: 0, y: CGFloat(self!.counter*100))
})
There are 2 issues:
Instead
timerLabel.tintColor = .white you need to use timerLabel.textColor = .white
2.To move starImage you need update constraint:
class ViewController: UIViewController {
let starImage = UIImageView(image: UIImage(systemName: "star.fill"))
let timerLabel = UILabel()
var counter = 0
var topConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
setupViews()
gameEngine()
}
private func setupViews() {
view.addSubview(starImage)
starImage.tintColor = .systemYellow
starImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(timerLabel)
timerLabel.tintColor = .white
timerLabel.textColor = .white
timerLabel.translatesAutoresizingMaskIntoConstraints = false
topConstraint = starImage.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
NSLayoutConstraint.activate([
starImage.heightAnchor.constraint(equalToConstant: 100),
starImage.widthAnchor.constraint(equalToConstant: 100),
topConstraint!,
starImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),
timerLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20),
timerLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
// PROBLEM HERE
private func gameEngine() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] _ in
self?.counter += 1
self?.timerLabel.text = "Timer: \(self!.counter)"
self!.topConstraint?.constant += 10
}
}
}

InputAccessoryView bug when dismiss controller and return ago

Who can help me? I create chat controller via UITableViewController, UINavigationController and I use InputAccessoryView. If I swipe screen to left (dismiss this controller) and return swipe to right (cancel dismiss) - table set adjustContentInset bottom to zero and InputAcessoryView close bottom content tableView. This problem created in ViewWillAppear event. My code:
UITableViewController:
// MARK: - Controller data
lazy var inputContainerView = ChatAccessoryView(frame: .zero, buttonSelector: #selector(sendMessage(sender:)), controller: self)
public var ticketID: Int = 0
private var lastMessageID: Int = 0
private var tableSections: [String] = []
private var tableRows: [[SupportTicketMessage]] = []
private var sendingMessage: Bool = false
private var URLTaskGetMessages: URLSessionDataTask?
private var URLTaskSendMessage: URLSessionDataTask?
// MARK: - Controller overrides
override func loadView() {
super.loadView()
tableView.register(ChatHeaderView.self, forHeaderFooterViewReuseIdentifier: "chatHeader")
tableView.register(ChatFromMessageTableViewCell.self, forCellReuseIdentifier: "chatFromMessage")
tableView.register(ChatToMessageTableViewCell.self, forCellReuseIdentifier: "chatToMessage")
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
if let messages = supportClass.getTicketMessages(ticketID) {
tableSections = messages.sections
tableRows = messages.rows
lastMessageID = messages.lastMessage
scrollTableToBottom(false)
} else {
tableView.setLoaderBackground("Загрузка сообщений...")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadMessages()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
URLTaskGetMessages?.cancel()
URLTaskSendMessage?.cancel()
}
override var inputAccessoryView: UIView? {
return inputContainerView
}
override var canBecomeFirstResponder: Bool {
return true
}
ChatAccessoryView:
class ChatAccessoryView: UIView {
// MARK: - Get params to init
let buttonSelector: Selector
let controller: UIViewController
// MARK: - Data
private let textView = ChatTextView()
private let sendButton = LoadingButton(frame: .zero, text: "Отпр.")
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
// MARK: - Init
required init(frame: CGRect, buttonSelector: Selector, controller: UIViewController) {
self.buttonSelector = buttonSelector
self.controller = controller
super.init(frame: frame)
configureContents()
blurEffectConfigure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overrides
override var intrinsicContentSize: CGSize {
return .zero
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
blurEffectConfigure()
}
override func didMoveToWindow() {
super.didMoveToWindow()
if let window = window {
textView.bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
}
}
// MARK: - Private methods
private func configureContents() {
backgroundColor = .clear
autoresizingMask = .flexibleHeight
textView.placeholder = "Напишите сообщение..."
textView.maxHeight = 160
textView.font = .systemFont(ofSize: 17)
textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
textView.layer.cornerRadius = 8
textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 4)
sendButton.titleLabel?.font = .boldSystemFont(ofSize: 17)
sendButton.setTitleColor(controller.view.tintColor, for: .normal)
sendButton.addTarget(controller, action: buttonSelector, for: .touchUpInside)
blurView.translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
sendButton.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(blurView)
self.addSubview(textView)
self.addSubview(sendButton)
blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
textView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8 ).isActive = true
textView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -20).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 48).isActive = true
sendButton.centerYAnchor.constraint(equalTo: textView.centerYAnchor).isActive = true
}
private func blurEffectConfigure() {
if #available(iOS 13.0, *) {
if traitCollection.userInterfaceStyle == .light {
blurView.effect = UIBlurEffect(style: .extraLight)
} else {
blurView.effect = UIBlurEffect(style: .dark)
}
}
}
// MARK: - Public methods
public func successSend() {
textView.text = ""
sendButton.loadingMode(false)
}
}
ChatTextView:
class ChatTextView: UITextView {
// MARK: - Data
var maxHeight: CGFloat = 0.0
public let placeholderTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = .clear
textView.isScrollEnabled = false
textView.isUserInteractionEnabled = false
textView.textColor = UIColor.black.withAlphaComponent(0.33)
return textView
}()
var placeholder: String? {
get {
return placeholderTextView.text
}
set {
placeholderTextView.text = newValue
}
}
// MARK: - Init
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
backgroundColor = UIColor.black.withAlphaComponent(0.06)
isScrollEnabled = false
autoresizingMask = [.flexibleWidth, .flexibleHeight]
NotificationCenter.default.addObserver(self, selector: #selector(UITextInputDelegate.textDidChange(_:)), name: UITextView.textDidChangeNotification, object: self)
placeholderTextView.font = font
addSubview(placeholderTextView)
NSLayoutConstraint.activate([
placeholderTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
placeholderTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
placeholderTextView.topAnchor.constraint(equalTo: topAnchor),
placeholderTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
colorThemeConfigure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overrides
override var text: String! {
didSet {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
override var font: UIFont? {
didSet {
placeholderTextView.font = font
invalidateIntrinsicContentSize()
}
}
override var contentInset: UIEdgeInsets {
didSet {
placeholderTextView.contentInset = contentInset
}
}
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if size.height == UIView.noIntrinsicMetric {
layoutManager.glyphRange(for: textContainer)
size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
}
if maxHeight > 0.0 && size.height > maxHeight {
size.height = maxHeight
if !isScrollEnabled {
isScrollEnabled = true
}
} else if isScrollEnabled {
isScrollEnabled = false
}
return size
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
colorThemeConfigure()
}
// MARK: - Private methods
private func colorThemeConfigure() {
if #available(iOS 13.0, *) {
if traitCollection.userInterfaceStyle == .light {
backgroundColor = UIColor.black.withAlphaComponent(0.06)
placeholderTextView.textColor = UIColor.black.withAlphaComponent(0.33)
} else {
backgroundColor = UIColor.white.withAlphaComponent(0.08)
placeholderTextView.textColor = UIColor.white.withAlphaComponent(0.33)
}
}
}
// MARK: - Obj C methods
#objc private func textDidChange(_ note: Notification) {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
Screenshots error:
TableView loaded (all right)
Swipe screen to left and cancel this action (swipe right)
When I returned to controller I see this Bug (last message go to bottom)
Thanks!
You could try something like this:
UITableViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateTableViewInsets()
}
private func updateTableViewInsets() {
let offSet = inputContainerView.frame.height
tableView?.contentInset.bottom = offSet
tableView?.scrollIndicatorInsets.bottom = offSet
}
Thank you all for your attention and trying to help me! I solved the problem as follows:
tableView.contentInsetAdjustmentBehavior = .never
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 53, right: 0)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
I disable automatic tableView.contentInsetAdjustmentBehavior and use tableView.conentInset. On open/close keyboard I change tableView.contentInset.
This works well for me, and pretty simple solution:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadInputViews()
prepareTableViewForViewPresented()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
prepareTableViewForViewDismissed()
}
private func prepareTableViewForViewPresented() {
self.tableView.contentInset.bottom = 0
}
private func prepareTableViewForViewDismissed() {
self.tableView.contentInset.bottom = view.safeAreaInsets.bottom + (inputAccessoryView?.frame.height ?? 0)
}
Known issues:
- when keyboard is shown, tableview still goes behind keyboard

How to access cell members (for animation) from scrollViewDidScroll method in UICollectionViewController?

How do I access members (an UIImage or UITextView) added to the view in a UICollectionViewCell from the scrollViewDidScroll method in the UICollectionViewController?
I would like to animate i.e. move the image and text at "different speed" while scrolling vertically to the next cell.
I understand that this can be done within the scrollViewDidScroll method but I don't know how to access the members.
the ViewController:
class OnboardingViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
**code here which I just can't figure out....**
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
collectionView?.register(PageCell.self, forCellWithReuseIdentifier: "cellId")
collectionView?.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
// this method "creates" the UIPageControll and assigns
setupPageControl()
}
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.currentPage = 0
pageControl.numberOfPages = data.count <--- data provided by a model from a plist - works perfectly
pageControl.currentPageIndicatorTintColor = .black
pageControl.pageIndicatorTintColor = .gray
pageControl.translatesAutoresizingMaskIntoConstraints = false
return pageControl
}()
private func setupPageControl() {
view.addSubview(pageControl)
NSLayoutConstraint.activate([
onboardingPageControl.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
onboardingPageControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
onboardingPageControl.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
])
}
all the other override methods are implemented in an extension and working fine i.e.
numberOfItemsInSection section: Int) -> Int {
return data.count
}
This is the PageCell:
class PageCell: UICollectionViewCell {
var myPage: MyModel? {
didSet {
guard let unwrappedPage = myPage else { return }
// the image in question:
myImage.image = UIImage(named: unwrappedPage.imageName)
myImage.translatesAutoresizingMaskIntoConstraints = false
myImage.contentMode = .scaleAspectFit
// the text in question
let attributedText = NSMutableAttributedString(string: unwrappedPage.title, attributes: [:])
attributedText.append(NSAttributedString(string: "\n\(unwrappedPage.description)", attributes: [:]))
myText.attributedText = attributedText
myText.translatesAutoresizingMaskIntoConstraints = false
myText.textColor = .black
myText.textAlignment = .center
myText.isEditable = false
myText.isScrollEnabled = false
myText.isSelectable = false
}
let myImage: UIImageView = {
let imageView = UIImageView()
return imageView
}()
let myText: UITextView = {
let textView = UITextView()
return textView
}()
fileprivate func setup() {
addSubview(myImage)
NSLayoutConstraint.activate([
myImage.safeAreaLayoutGuide.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 60),
myImage.centerXAnchor.constraint(equalTo: centerXAnchor),
myImage.leadingAnchor.constraint(equalTo: leadingAnchor),
myImage.trailingAnchor.constraint(equalTo: trailingAnchor),
myImage.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.4)
])
addSubview(myText)
NSLayoutConstraint.activate([
myText.topAnchor.constraint(equalTo: myImage.bottomAnchor, constant: 16),
myText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
myText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can achieve this by getting current visible cells of your collectionView, and it would be great if you access them in scrollViewDidEndDecelerating intead of scrollViewDidScroll.
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in collectionView.visibleCells {
//cell.imageView
//cell.txtView
// you can access both imageView and txtView here
let indexPath = collectionView.indexPath(for: cell)
print(indexPath) // this will give you indexPath as well to differentiate
}
}

Animate constraints change UIViewController

I have "error" view, that is placed above navigation bar and hidden. When error occured, i want that view to smoothly show from top. I tried:
class AuthViewController: UIViewController {
let error: ErrorView = {
let error = ErrorView()
error.setup()
return error
}()
var topAnchor: NSLayoutConstraint!
var botAnchor: NSLayoutConstraint!
override func viewDidLoad() {
setupErrorView()
}
private func setupErrorView(){
view.addSubview(error)
botAnchor = error.bottomAnchor.constraint(equalTo: view.topAnchor)
botAnchor.isActive = true
topAnchor = error.topAnchor.constraint(equalTo: view.topAnchor, constant: CGFloat(Offsets.navigationAndStatusBarHeight))
topAnchor.isActive = false
error.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
error.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
func showError(_ text: String){
UIView.animate(withDuration: 2.0) {[weak self] in
guard let weakSelf = self else { return }
print("attempt to animate")
weakSelf.error.show(text)
weakSelf.botAnchor.isActive = false
weakSelf.topAnchor.isActive = true
weakSelf.view.setNeedsLayout()
}
}
}
class ErrorView: UIView {
private var label: UILabel = {
return LabelSL.regular()
}()
fileprivate func setup(){
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = Theme.Color.orange.value
addSubview(label)
}
fileprivate func show(_ text: String){
let sideOffset: CGFloat = 10
let verticalOffset: CGFloat = 10
label.text = text
label.topAnchor.constraint(equalTo: topAnchor, constant: verticalOffset).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor, constant: sideOffset).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor, constant: -sideOffset).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalOffset).isActive = true
}
}
Animation should be done when func showError(_ text: String){ method called, but it's not. View just appear instantly.
You're trying to animate constraints in wrong way. You should set constraints outside of animation block and only layoutIfNeeded in animation:
func showError(_ text: String){
botAnchor.isActive = false
topAnchor.isActive = true
error.show(text)
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}

Resources