I think I found a fix - calling self.view.layoutIfNeeded() in the animations block.
Here is my reproduction:
import UIKit class ViewController: UIViewController { var showB = true weak var viewB: UIView! override func viewDidLoad() { super.viewDidLoad() let viewA = UIView() viewA.backgroundColor = UIColor.green let toggleViewBButtonAnimated = UIButton(frame: CGRect(x: 50, y: 150, width: 200, height: 40)) toggleViewBButtonAnimated.backgroundColor = UIColor.cyan toggleViewBButtonAnimated.setTitle("Toggle B (animated)", for: .normal) viewA.addSubview(toggleViewBButtonAnimated) toggleViewBButtonAnimated.addTarget(self, action: #selector(toggleBButtonTappedAnimated), for: .touchUpInside) let viewB = UIView() viewB.backgroundColor = UIColor.orange let viewBHeightConstraint = viewB.heightAnchor.constraint(equalToConstant: 200) viewBHeightConstraint.priority = 999 viewBHeightConstraint.isActive = true self.viewB = viewB let stackView = UIStackView(arrangedSubviews: [viewA, viewB]) stackView.axis = .vertical stackView.alignment = .fill stackView.distribution = .fill stackView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(stackView) stackView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true } @IBAction func toggleBButtonTappedAnimated() { self.showB = !self.showB UIView.animate(withDuration: 0.3, animations: { self.viewB.isHidden = !self.showB; self.view.layoutIfNeeded() } ) } }
This controller sets up a UIStackView , which has two vertical views, green (A) and orange (B). Pressing the button hides / hides view B.
If I don't have self.view.layoutIfNeeded() in the animations block, then when view B is displayed, it flies to the top of the screen. (When view B is hidden, it usually hides - moving down from the bottom of the screen.)
When I added self.view.layoutIfNeeded() to the animations block, view B shows as expected - it appears at the bottom of the screen.
Thanks to the answer from @ g3rv4 for pointing me in that direction!
source share