6

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.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
kiwisip
  • 437
  • 1
  • 4
  • 12
  • 1
    Did you ever find a solution for this? I'm having a similar problem with the views flying from the top of the screen - although my stack view is set to `Fill` distribution (the default). Using the Xcode 9 GM seed & iOS 11 GM seed. – Matt Cline Sep 13 '17 at 07:03
  • 1
    Calling `self.view.layoutIfNeeded()` just before setting `stackView.distributon = .fillProportionally` helped. – kiwisip Sep 14 '17 at 11:39
  • @MattCline I am having same problem ... :(. Any Idea how to fix it ? – kmithi1 Sep 27 '17 at 19:10

3 Answers3

12

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!

Matt Cline
  • 286
  • 1
  • 6
  • Great! Calling `self.view.layoutIfNeeded()` just before setting `stackView.distributon = .fillProportionally` helped! Thank you. – kiwisip Sep 14 '17 at 11:38
  • You should not change the hidden property of a `UIStackView`'s `subview` in the animation block as there was a bug in iOS <= 10 and the animation will look weird in some cases in iOS 11. – Iulian Onofrei Oct 11 '17 at 16:31
  • Adding self.view.layoutIfNeeded() to the end of the animation block fixed it for me . The stackview was previously being animated from the top of the screen, but not it animates from the proper point. Thanks! – vikzilla Dec 21 '17 at 20:05
2

For me the problem was a behavior regression introduced in iOS 11 and had nothing to do with the UIStackView distribution type. I tried a couple of code-based solutions to no avail: firstly, calling layoutIfNeeded on the container hierarchy; secondly, (as seen in another answer to a similar question) tweaking the views' contentMode.

My hacky solution is the following:

Add a new, zero-width UIView placeholder on the side of the UIStackView where the show animation was incorrectly bringing items in from the edge of the screen.

AlexD
  • 813
  • 9
  • 15
  • Amazing. It just works. I will spend the rest of my life wondering how you came up with this solution :) Saint-Steve must have inspired you. – Jean Le Moignan Jan 13 '19 at 21:38
1

I encountered the same issue using the GM seed this morning. I've ended up using the UIView.performWithoutAnimation { block to do any UIStackView modifications where animations are not desired for the time being.