13

I am using a Stack view to create a kind of table UI, I have 6 views in a StackView 0,2,4 are visible and 1,3,5 are hidden. When tapping one of the visible views I wish to "open" one of the views that are hidden.

I have this code that works great on iOS 10 but from some reason I can not understand it is not working well on iOS 9.

Note that if I load the views all open, the close animation will work but it won't open when setting the hidden property to false.

Here is my code -

EDIT After some debugging looks like the view height constraint is nor recovering from the hiding, and it's frame is still height is 0.

 import UIKit

class DeckView: UIView {

}

class ViewController: UIViewController {

var scrollView: UIScrollView!
var stackView: UIStackView!

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(scrollView)

    view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
    view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))


    stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.spacing = 0
    stackView.alignment = .center
    stackView.distribution = .fillProportionally
    stackView.axis = .vertical
    scrollView.addSubview(stackView)

    scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[stackView]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))
    scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[stackView]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))


    for i in 0 ..< 8 {
        let view  = DeckView()
        view.tag = i
        view.translatesAutoresizingMaskIntoConstraints = false
        view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
        view.isUserInteractionEnabled = true

        if i%2 == 0 {
            view.backgroundColor   = UIColor.magenta
            let constriant = view.heightAnchor.constraint(equalToConstant:160)
            constriant.priority = 999
            view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.openDeck(_:))))
            view.addConstraint(constriant)

        } else {
            view.backgroundColor   = UIColor.red
            let constriant = view.heightAnchor.constraint(equalToConstant:160)
            constriant.priority = 999
            view.addConstraint(constriant)
            view.isHidden = false
        }

        stackView.addArrangedSubview(view)
    }
}

func openDeck(_ sender:UIGestureRecognizer) {
    if let view = sender.view as? DeckView,
    let childView = stackView.viewWithTag(view.tag + 1) {
            UIView.animate(withDuration: 0.4, animations: {
                childView.isHidden = !childView.isHidden
            })
    }
}
}
shannoga
  • 19,649
  • 20
  • 104
  • 169

2 Answers2

49
  1. keep the view's height priority lower than 1000(go for 999).
  2. Do not set setHidden:true if it is already hidden(This is UIStackView's bug)
  • Baaaam you saved my live!!! Where is this bug documented?! Can you post a link here? I need to document this im my code... – blackjacx Jun 28 '17 at 12:09
  • I mean if you can post the radar or bug id – blackjacx Jun 30 '17 at 08:56
  • 6
    I can't thank you enough for this. Both the **problem** from 2015 (radars [UIStackView: toggleing hidden with animations gets stuck in hidden mode](http://www.openradar.me/22819594) and [Calling setHidden:NO on a subview of UIStackView does not always show it](http://www.openradar.me/25087688) as requested by @blackjacx ) and **solution** from step 2 are still valid in August 2017. I should mention that I didn't need to carry out step 1 (and `distribution = .fill` was fine for me). This bug is clearly connected to animation, as it didn't surface when I toggled `isHidden` instantly. – Jamie Birch Aug 22 '17 at 10:29
  • 2
    [This article](https://useyourloaf.com/blog/stack-view-constraint-conflicts-when-hiding-views/) and [this article](http://angelolloqui.com/blog/36-Oddities-of-UIStackView) have good explanations behind the reasoning of vats's Step 1. In summary: *"Basically, what happens here is that a vertical stack view will automatically set a view's height to 0 px to hide it. However, if it has conflicting constraints like padding that might try to keep it, say, 16 px tall, then you will need to lower the priority of those constraints to allow it to be shortened to 0 px height when hiding."* – Jamie Birch Aug 22 '17 at 11:05
  • 1
    @vats This isHidden animation bug has been stuck with me for weeks. Thanks a lot! – HuaTham Sep 19 '17 at 09:49
  • Cannot tell you how long I've been banging my head about this. Thought I was going insane. Thank you soooo much! – Adam Young May 23 '18 at 08:19
  • 3
    This is still a thing in iOS 13 -_____________________-' – J. Doe Aug 02 '19 at 09:59
  • You saved my life, I thought I was doing something wrong but actually it was that weird bug, still in iOS 13. That's crazy! – Rico Crescenzio Apr 15 '20 at 09:07
  • This should be the accepted answer! You saved me. Thank you!! – Ondřej Korol Oct 23 '20 at 12:56
2

If any one stumble on this issue.

I was able to solve this issue by removing the -

stackView.distribution = .fillProportionally

I am not sure why this happened but I found that Autolayout added a height constraint named 'UISV-fill-proportionally' with a constant of 0 and greater priority then my height constraint. removing the fillProportionally fixed the issue.

shannoga
  • 19,649
  • 20
  • 104
  • 169