2

Preface: I took a peek at this post and it didn't solve my problem. Here's a link to a repo that shows the error I'm encountering.

I have a UIViewController with a vertical UIStackView containing nested horizontal UIStackViews.

There are two UIStackViews that are toggled. If UIStackViewNumber1 is displayed, UIStackViewNumber2 is hidden & vice versa. I originally set the view to display both, but it's too crowded.

UIStackView
    stackViewThatDoesntMove
    stackViewNumber1
    stackViewNumber2
    stackViewThatDoesntMove

Everything works fine with no AutoLayout errors if I don't hide anything. Each of the UIStackViews has nested stackViews containing buttons, labels, sliders, etc. I found the view was too crowded, so I thought I'd set hide one of stackViews and animate the change, as follows:

stackViewNumber2.isHidden = true

UIView.animate(withDuration: 0.33,
               options: [.curveEaseOut],
               animations: {
                self.stackViewNumber1.layoutIfNeeded()
                self.stackViewNumber2.layoutIfNeeded()
                self.view.layoutIfNeeded()
                    },
               completion: nil)
}

While I got the desired result in Simulator and on a physical device, the moment I hide one of the UIStackViews, my console fills up with AutoLayout errors.

I took a look at this post and my problem seemed pretty straightforward. I went with the approach with the least "bookkeeping" and wrapped stackViewNumber2 in a UIView. I end up with the same set of errors I originally had without wrapping stackViewNumber1 in a UIView.

I also read Apple's documentation on UIStackView and tried playing around with the arrangedSubviews() to set constraints isActive = false, as follows:

for subview in stackViewNumber2.arrangedSubviews {
    for constraint in subview.constraints {
        constraint.isActive = false
    }
}

stackViewNumber2.updateConstraints()
// then run animation block to update view

Is there a more efficient way to hide nested UIStackViews without adding an IBOutlet for constraint, making sure it's not weak, and doing bookkeeping on whether isActive is true/false?

I think my problem has to do with nested stackViews, but I'm unable to figure out how to address it. Thank you for reading and I welcome suggestions on how to hide a UIStackView containing nested UIStackViews.

Community
  • 1
  • 1
Adrian
  • 16,233
  • 18
  • 112
  • 180
  • Does changing hidden property inside the animation block have any different effect. Leaving off layout if needed? – agibson007 Mar 17 '17 at 01:58
  • @agibson007 Thanks for reading. No, it does not have any different effect. – Adrian Mar 17 '17 at 02:02
  • What do the nested stackviews contain? Are they sizing themselves with content or are you setting it. Also what's the setup vertical holding horizontal. Just trying to picture it and what might be the hold up. Also instead of deactivating the constraints and instead of hiding the inner stackview what happens if you hide the subviews of those stackview in an animation block. – agibson007 Mar 17 '17 at 02:09
  • One is a horizontal stack of three UIButtons. The others are UISliders with a UITextField next to them (horizontal as well). – Adrian Mar 17 '17 at 02:12
  • what about the outside stackview? I am going to run a test – agibson007 Mar 17 '17 at 02:33
  • I dump everything in the main stackView. Top, Left, and Right are pinned to the edge of the view. – Adrian Mar 17 '17 at 02:48
  • The UIStackViews at the top of my question are vertical stack views. The nested stack views are horizontal. – Adrian Mar 17 '17 at 02:53
  • So does stackViewNumber1 contain another stackview and are both horizontal? Or do you have a vertical containing a vertical anywhere other than the top level stackview holding 1 vertical being vertical itself? But so far I have been unable to create any autolayout errors on hiding even with stackview->[stackview->stackview] stackview, stackview – agibson007 Mar 17 '17 at 03:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138283/discussion-between-adrian-and-agibson007). – Adrian Mar 17 '17 at 03:33

2 Answers2

3

Instead of hiding I think it would be better to remove the stackView itself.

myStackView.removeArrangedSubview(playButton)
playButton.removeFromSuperview()

Hope it helps.

Md. Ibrahim Hassan
  • 5,359
  • 1
  • 25
  • 45
  • 1
    Much cleaner, no errors and no bookkeeping! I'm going to post my final solution below, which is based largely on yours. Thank you! – Adrian Mar 17 '17 at 13:15
2

I started out wanting to animate hiding one view and unhiding another, but at Md. Ibrahim Hassan's suggestion, I tried removing the stackViewNumber1 from the superview and replacing it with stackViewNumber2. The end result is much cleaner with minimal bookkeeping, no dragging a bazillion @IBOutlets onto the viewController for updating constraints, etc.

The views of parentStackView are stored in the stack's arrangedSubviews() array in the order they're displayed in Main.storyboard.

enter image description here

In this example, the arrangedSubviews array looks like this:

[topStackView, stackViewNumber1, stackViewNumber2, stopButton]

I only need to know about parentStackView, stackViewNumber1, and stackViewNumber2, so I dragged 3 outlets to the storyboard, as follows:

// StackViews the viewController needs to know about...
@IBOutlet weak var parentStackView: UIStackView!
// note weak is removed from the following 2 stackViews
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

From there, if I want to remove stackViewNumber2, the following code removes it:

parentStackView.removeArrangedSubview(stackViewNumber2)
// parentStackView.arrangedSubviews() = [topStackView, stackViewNumber1, stopButton]
stackViewNumber2.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber1, at: 1)
// parentStackView.arrangedSubviews() = [topStackView, stackViewNumber1, stopButton]

Now, let's say I want to take out stackViewNumber1 and replace it with stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber1)
// parentStackView.arrangedSubviews() = [topStackView, stopButton]
stackViewNumber2.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1) 
// parentStackView.arrangedSubviews() = [topStackView, stackViewNumber2, stopButton]

Recall stackViewNumber1 and stackViewNumber2 are not weak. The reason is I want to keep them around so I can swap them in and out. I didn't see any adverse impact on memory usage as a result of changing the 2 UIStackView outlets to weak.

End result: The slew of AutoLayout errors I encountered with my initial approach is gone without a ton of bookkeeping.

Update:

With a little more tinkering, I got animation to work, too. Here's the code:

    UIView.animate(withDuration: 0.25,
                   delay: 0,
                   usingSpringWithDamping: 2.0,
                   initialSpringVelocity: 10.0,
                   options: [.curveEaseOut],
                   animations: {
                    self.view.layoutIfNeeded()
    },
                   completion: nil)
Community
  • 1
  • 1
Adrian
  • 16,233
  • 18
  • 112
  • 180