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()
})
}
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()
})
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With