1
votes

Numerous tutorials on animating AutoLayout constraints suggest to update constant property of a constraint and then call layoutIfNeeded() in animation block.

My situation is a bit tricky. I have a view that houses 3 subviews. The height of this superview is not fixed - it is calculated as a sum of heights of its subviews. On some event, I ask one of those 3 subviews to toggle its height (it changes between 0 and 30, i.e. I want to smoothly hide and show it). The code is similar to this:

// In superview
subview1.isVisibleInContext = !subview1.isVisibleInContext

// In subview
class MySubview: UIView {
    @IBOutlet var heightConstraint: NSLayoutConstraint!

    var isVisibleInContext = false {
        didSet {
            updateHeight()
        }
    }

    func toggleHeight() {
        let newHeight = isVisibleInContext ? 30 : 0
        layoutIfNeeded()
        heightConstraint.constant = newHeight
        UIView.animate(withDuration: 0.8) {
            self.layoutIfNeeded()
        }
    }
}

Unfortunately, this does not work as I expect. I can see the smooth change of the height of my subview, but the height of my superview is recalculated immediately after I set the new value for my subview height constraint.

I want to see the height of the superview gradually increasing/decreasing as on of its subviews grows or decreases.

Please someone point me in the right direction. Any help is appreciated.

Thanks a lot!

2

2 Answers

6
votes

The animation block should be in the UIView that contains the 3 MySubviews. Inside the MySubview class you only update the height constraint's constant:

In Subview

func toggleHeight() {
   let newHeight = isVisibleInContext ? 30 : 0
   heightConstraint.constant = newHeight
}

Then in the UIView that contains the 3 MySubviews you animate the change:

In Superview

func toggleHeight(with subview: MySubview) {
   subview.toggleHeight()
   UIView.animate(withDuration: 0.8) {
      self.view.layoutIfNeeded()
   }
}

enter image description here

1
votes

The first thing, that was incorrect in my approach was executing self.layoutIfNeeded(). Having investigated the issue I learned out that I had to execute it on the superivew, like this:

self.superview?.layoutIfNeeded()

That also didn't work out for a reason. The main issue in this case was that the view, which had 3 subviews inside was itself a subview of view. So to make it work I had to use the following code:

self.superview?.superview?.layoutIfNeeded()

Definitely not good that subview has to know the hierarchy of views, however it works.