Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stackview animation - collapse of arranged subviews

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()
    })

}
like image 702
dorsz Avatar asked Nov 13 '15 19:11

dorsz


2 Answers

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()
    })
}
like image 126
dorsz Avatar answered Oct 04 '22 22:10

dorsz


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.

like image 27
Steven Avatar answered Oct 05 '22 00:10

Steven