I have a UILabel inside a UIStackView, and the stack view is inside another UIScrollView, I'm using auto layout. The label has single line (ie. numberOfLines equal to 1) and in a few cases I need to set it to multiline (ie. numberOfLines equal to 0) with an animation that expands it.
func expand() {
label.numberOfLines = 0
}
when I click expand:
messageView.expand()
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
However, when it expands, the label's frame isn't updated and I have to scroll (the scroll view) to make it fully visible. What could be wrong?
Thanks!
Have you changed the label's text?
Check if the autolayout is correct
Animation of UILabel doesn't perform within
UIView.animate(withDuration:).
This should work:
UIView.transition(with: label, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.label.numberOfLines = 0
})
You can experiment with options and execution block.
Related
I have a Header view that contains pager view (FSPagerView), which has an item (FSPagerViewCell) that contains bar chart, 2019, year review texts. I'm trying to scale down Header at certain times with such code:
private func setSelfHeightAnimated(height: CGFloat)
{
UIView.animate(withDuration: 0.2, animations: {
self.selfHeightAnchor.constant = height
self.superview?.layoutIfNeeded()
})
}
private func setPagerHeightAnimated(height: CGFloat)
{
UIView.animate(withDuration: 0.2, animations: {
self.pagerHeightAnchor.constant = height
self.layoutIfNeeded()
// self.pagerView.layoutIfNeeded()
// self.pagerView.layoutSubviews()
// self.pagerView.cellForItem(at: 0)?.layoutIfNeeded()
// self.pagerView.cellForItem(at: 0)?.setNeedsDisplay()
// self.superview?.layoutIfNeeded()
// self.setNeedsDisplay()
// self.pagerView.setNeedsDisplay()
})
}
However, the old frame glitch is very visible. What could be missing? Here is the video: https://streamable.com/owsdi
You have an 1st layout and a 2nd layout, and an animation that leads from the 1st to the 2nd.
Currently, the 1st layout is faded out and the 2nd layout is faded in during the animation, which gives you the „glitch“.
I assume what you want to achieve is:
- The top part (History, 2019, Year review) should keep their height, and
- only the columns below should shrink/expand, while the lower table slides up or down.
A solution probably is that you animate only the height of a subview that contains the 2 columns.
If the header view is set to adopt its height to its contents, then I expect that the required effect is shown without a glitch.
EDIT:
If you definitively had to animate the upper part as a whole, one possibility may be (that I consider as ugly) to set the alpha value of History, 2019, and Year to 0 immediately before the animation begins. Then these texts would only fade in so the glitch with double images at different places would be avoided.
Try like this to avoid glitching.
private func setSelfHeightAnimated(height: CGFloat)
{
self.selfHeightAnchor.constant = height
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
self.viewDidLayoutSubviews()
})
}
private func setPagerHeightAnimated(height: CGFloat)
{
self.pagerHeightAnchor.constant = height
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
self.viewDidLayoutSubviews()
})
}
I have an animation who modify the constraint of an UIView and i need to know the size of this UIView after animate this, but before the animation begin...
storyboard
When the user scroll the UITableView I update the heightConstraint of the black UIView, I need the height after update the UIView because i need in the function viewDidLayoutSubviews to fixe the height of the Yellow UIview.
this is the code I use for animate the UIView:
self.headerIsCollapsed = true
self.heightProfilView.constant -= (self.view.bounds.height / 5)
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
self.imageUserProfil.layer.opacity = 0
})
and i need the value here :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
heightTopBar.constant = // here i need the height of the UIView after animating this
}
So, the question is: How I can pre-calculate the height of the UIView after animating this ?
Simply use a completion block of UIView.animate()
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
self.imageUserProfil.layer.opacity = 0
}) { (finished) in
// get the view's height after animation here
}
Unless you specified duration as 0, the completion block will execute after animation sequence ends.
Try this:
self.headerIsCollapsed = true
//create this property at instance level in your viewcontroller class
self.heightBeforeAnimation = self.heightProfileView.frame.size.height
self.heightProfilView.constant -= (self.view.bounds.height / 5)
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
self.imageUserProfil.layer.opacity = 0
}){ (isCompleted) in
//create this property at instance level in your viewcontroller class
self.heightAfterAnimation = self.heightProfileView.frame.size.height
}
and call whatever function and use the value of two variables containing value of height before and after animating your view.
I have a cell with a label and a button if I click the button the height of the cell should increase and an image should get visible.
I set up autolayout with a height constraint for the image that I change, when the button is clicked.
The constraint that I use on init is displayed correctly (e.g. if I set the constant to 0 or 200). But if I change the constant in the button action, nothing happens.
Here is the logic for the constraint
func toggleImageHeight(){
cellIsExpanded = !cellIsExpanded
self.imageHeightConstraint.constant = self.cellIsExpanded ? 200 : 0
UIView.animate(withDuration: 2) {
self.layoutIfNeeded()
}
}
Your code is not proper. please try this.
self.imageHeightConstraint.constant = self.cellIsExpanded ? 200 : 0
UIView.animate(withDuration: 2) {
self.layoutIfNeeded()
}
this will work.
Using a Storyboard, I have a height constraint with 2 size class (one for regular at 200pt, one for compact at 100pt).
Because I'm animating it when the view appears, the height of the element goes from 0 (initial state) -> 200pt for regular or 100pt for compact (final state).
It is a simple "zoomIn" animation.
But the thing is that because I change programmatically the constant, I'm losing the class sizes meaning when I rotate the phone, I have to set the constant to the right size instead of having Interface Builder's automatic class size.
So how would you apply an animation to an UIElement with auto-layout (and without having to create spaghetti code in viewWillLayoutSubviews, viewDidLayoutSubviews)?
Without your code, it's not clear exactly what you're doing, but here goes anyway :) First, the best way to animate when you are using autolayout is to animate the constraint changes, e.g.:
myConstraint.constant = myConstraintInitialConstant
UIView.animateWithDuration(animationSpeed) {
self.view.layoutIfNeeded()
}
The most important thing to note here is that the constraint change is outside the animation block, what you animate is layoutIfNeeded().
But you want to know what your initial constant was when the nib was loaded, yes? Then save it in viewDidLoad(), e.g.:
private var myConstraintInitialConstant: CGFloat = 65
override func viewDidLoad() {
super.viewDidLoad()
myConstraintInitialConstant = myConstraint.constant
}
Did you try to play with the layoutIfNeeded() / layoutSubviews() methods ? This will update the frame of your UIElement after added new constraints to it
I had a top constraint set to 0 and I animate it like that :
override func layoutSubviews() {
super.layoutSubviews()
// I reset my constraint's constant if already animate
if(topConst.constant > 0){
topConst.constant = 0
}
self.viewToAnimate.layoutIfNeeded()
//Animate the constraint
topConst.constant = 100
UIView.animateWithDuration(0.8, delay: 0.2, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: .CurveEaseIn, animations: { () -> Void in
self.viewToAnimate.layoutIfNeeded()
}, completion: nil)
}
I used layoutSubviews it works fine :D
I have expandable UILabel:
func expandDescriptionLabel() {
let calculatedHeightDescriptionLabel = lblDescription.sizeThatFits(CGSizeMake(lblDescription.frame.width, CGFloat.max)).height
expanded = true
UIView.animateWithDuration(0.3, animations: {
self.cnsDescriptionHeight.constant = calculatedHeightDescriptionLabel
self.hideMoreButton()// this line means nothing, results are the same with this line commented out
self.view.layoutIfNeeded()
})
}
func reduceDescriptionLabel() {
expanded = false
self.cnsDescriptionHeight.constant = 60
UIView.animateWithDuration(0.3, animations: {
self.showMoreButton() // this line means nothing, results are the same with this line commented out
self.lblDescription.setNeedsLayout();
self.view.layoutIfNeeded()
})
}
When expanding animation looks just fine, but when height is decreasing strange things happen. Look at GIF below. I tried already CGAffineTransformScale and putting UILabel in UIView and then animating a UIView.
Question is how to make the last part of animation more smooth.
As you can see when Labels height is decreasing, text is moving up and then suddenly appear in correct place(without animation).