stackview animation - collapse of arranged subviews
Asked Answered
P

2

10

I have to use stackview as parent view. I'm trying to animate stackview with 2 rows to get an effect of collapsing and inflating the bottom row. You can say that, what I'm trying to do is the same thing you get when you apply this code to normal autolayouted view with subviews:

func showView()
{
    if(!expand)
    {  UIView.animateWithDuration(0.5, animations:{
        self.expandableViewHeight.constant = 50
        // Update layout of all subviews
        self.parentViewController!.view.layoutIfNeeded()})
    } else {   
      UIView.animateWithDuration(0.5, animations:{
        self.expandableViewHeight.constant = 100
        // Update layout of all subviews
        self.parentViewController!.view.layoutIfNeeded()})
    }
}

Simple and neat. When you let's say click on this view, it expands or collapses with all it's subviews. The subviews are clipped (not resized/rescaled), as the animation goes, and finally you can see only half of the original view.

It looks like a simple thing, but I can't get it done with stackview. When I'm adding second row to stackview and then animate layoutIfNeeded(), as in several tutorials, then : - when inflating the bottom row comes from the left to it's position, - when collapsing the bottom row just disappears (no animation) - only background view is animated properly (see the code)

When I'm using height constraints and don't animate on layoutIfNeeded() on the bottom row, then: - when inflating the bottom row is rescaled to full height as the animation goes - when collapsing - the bottom row rescaled to 0 as the animation goes

Cannot make it clip the bottom row! Any tips appreciated! :)

This is my stackview code:

override func viewDidLoad(){
    super.viewDidLoad()

    background = UIView(frame:CGRectMake(0, 0, frame.width,frame.height))
    background.backgroundColor = UIColor.whiteColor()
    background.layer.cornerRadius = 5
    background.layer.masksToBounds = true

    self.addSubview(background)

    vStack = UIStackView()
    vStack.axis = .Vertical
    vStack.alignment = .Fill
    vStack.distribution = .Fill
    vStack.spacing = 0

    self.addArrangedSubview(vStack)

    hStack = UIStackView()
    hStack.axis = .Horizontal
    hStack.alignment = .Center
    hStack.distribution = .EqualSpacing
    hStack.spacing = 10

    hStack2 = UIStackView()
    hStack2.axis = .Horizontal
    hStack2.alignment = UIStackViewAlignment.Center
    hStack2.distribution = UIStackViewDistribution.EqualCentering
    hStack2.spacing = 10

    lquestionStack = UIStackView()
    questionStack.axis = .Horizontal
    questionStack.alignment = .Center
    questionStack.distribution = .EqualSpacing
    questionStack.spacing = QUESTION_PADDING


    let labelQuestionNumber = UIButton()
    labelQuestionNumber.userInteractionEnabled = false
    let numberImage = UIImage(named: "backgroundImage")
    labelQuestionNumber.setBackgroundImage(numberImage, forState: .Normal)

    questionStack.addArrangedSubview(labelQuestionNumber)


    hStack.addArrangedSubview(questionStack)
    vStack.addArrangedSubview(hStack)

    hStack2.addArrangedSubview(questionStack)
    vStack.addArrangedSubview(hStack2)

 }
 public func collapseInflateAction() {
    if checkWidget.collapsed {
        inflateWidget()
    } else {
        collapseWidget()
    }
    checkWidget.collapsed = !checkWidget.collapsed
}

private func collapseWidget(){
    vStack.removeArrangedSubview(self.hStack2)
    self.hStack2.removeFromSuperview()

    UIView.animateWithDuration(0.5, animations: { () -> Void in
        self.background.frame.size.height = (self.background.frame.size.height)/2
        self.layoutIfNeeded()
        self.superview?.layoutIfNeeded()
        })
}

private func inflateWidget(){
    self.vStack.addArrangedSubview(self.hStack2)

    UIView.animateWithDuration(0.5, animations: { () -> Void in
        self.background.frame.size.height = self.background.frame.size.height*2
        self.layoutIfNeeded()
        self.superview?.layoutIfNeeded()
    })

}

And this is the variation of the 2 last methods that uses constraints instead of layoutIfNeeded() animation. And also the constraint being modified:

override func viewDidLoad(){
    super.viewDidLoad()
(...)
 heightConstraint = NSLayoutConstraint(item: hStack2, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 0)
    self.addConstraint(heightConstraint)
}
private func collapseWidget(){
    UIView.animateWithDuration(0.5, animations: { () -> Void in
        self.background.frame.size.height = 44
        self.heightConstraint.constant = 0
        self.superview?.layoutIfNeeded()
        })
}

private func inflateWidget(){
    UIView.animateWithDuration(0.5, animations: { () -> Void in
        self.background.frame.size.height = 88
        self.heightConstraint.constant = 44
        self.superview?.layoutIfNeeded()
    })

}
Perfect answered 13/11, 2015 at 19:36 Comment(1)
Was wondering whether you ever found a solution to your issue, given how old your question is. Mind sharing it?Amarillo
P
3

That was a long time ago, but I think it helped to add self.layoutIfNeeded() before layouting superview

   fileprivate func collapseWidget(){

    UIView.animate(withDuration: 0.25, animations: { () -> Void in
        self.background.frame.size.height = self.heightCollapsed
        self.heightConstraint.constant = self.heightCollapsed
        self.layoutIfNeeded()
        self.superview?.layoutIfNeeded()
        })
}

fileprivate func inflateWidget(){
    self.separatorView.isHidden = false
    UIView.animate(withDuration: 0.25, animations: { () -> Void in
        self.background.frame.size.height = self.heightInflated
        self.heightConstraint.constant = self.heightInflated
        self.layoutIfNeeded()
        self.superview?.layoutIfNeeded()
    })
}
Perfect answered 3/2, 2017 at 7:8 Comment(1)
I don't know why this is needed but it works. I only had to change self.layoutIfNeeded() by self.superview?.layoutIfNeeded() (no need for both)Phagocyte
H
3

Something that could help people to get their animation working smoothly in this case, is to trigger layoutIfNeeded() on the highest view in the hierarchy.

This is particularly helpful when your UIStackView is embedded into an UIScrollView. If you only trigger layoutIfNeeded on your arrangedSubviews or on the UIStackView you will end up with weird glitches in your animation.

Another solution is too simply trigger layoutIfNeeded on your UIViewController view.

Hope this answer will help.

Hoopla answered 15/5, 2020 at 13:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.