Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 11 UIStackView fill proportionally distribution causes weird view loading animation

If I set:

stackView.distributon = .fillProportionally 

Then on iOS 11 I get a very weird animation when loading a view that contains this stack view (all subviews - not just stack view - are flying from the top or bottom of the screen). On lower iOS version everything works fine. If I set the stack view's distribution to anything else, everything works fine as well.

Does anyone have any idea what could be a cause of this problem?

Thank you.

like image 505
kiwisip Avatar asked Aug 25 '17 12:08

kiwisip


1 Answers

I think I've found a fix - call self.view.layoutIfNeeded() in the animations block.

Here's 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 that has two vertical views, a green one (A) and an orange one (B). Tapping the button hides/unhides view B.

If I do not have self.view.layoutIfNeeded() in the animations block, then, when view B is being shown, it flies in from the top of the screen. (When view B is being hidden, it hides normally - moving down off the bottom of the screen.)

When I added self.view.layoutIfNeeded() to the animations block, view B shows as expected - it comes up from the bottom of the screen.

Thanks to an answer from @g3rv4 for pointing me in this direction!

like image 94
Matt Cline Avatar answered Oct 13 '22 03:10

Matt Cline