Animate NSLayoutConstraint created programmatically - ios

Something special has to be done to animate a constraint instantiated programmatically?
I instantiate a constraint programmatically as a lazy variable:
private lazy var topConstraint: NSLayoutConstraint = {
let constraint = view.topAnchor.constraint(equalTo: otherView.topAnchor, constant: otherView)
return constraint
}()
Then, I animate a change in the constant of this constraint:
topConstraint.constant = newValue
view.setNeedsLayout()
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
It is not working... I also have tried putting the constant setting into the block:
UIView.animate(withDuration: 1) {
self.topConstraint.constant = newValue
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
But I achieved nothing at all, I've tried almost all the combinations. The constraint constant is properly changed, but always without animation.
PD: I also gave a shot to UIViewPropertyAnimator.

No need for setNeedsLayout().
self.topConstraint.constant = 0.0
UIView.animate(withDuration: 1.0) {[weak self] in
self?.topConstraint.constant = 10.0
self?.view.layoutIfNeeded()
}

Related

Layout is Broken when I update the Table View Height Constraint programmatically

when I'm trying to update my table view height constraint constant it causes a bug in the view.
What I am doing wrong?
func setupViewHeight() {
// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
// at some other point in time we change the constraints and call the animator
self.tableHeightConstraint.constant = CGFloat(self.previousClinics.count) * self.cellHeight
self.view.setNeedsLayout()
animator.startAnimation()
self.tableView.reloadData()
}
Wrapper View Subview Constraints
Wrapper View Constraints
Buggy View in Action (video)
You can try one thing -> change tableview height constraint priority to 100 and try to remove all fixed height of cell.
Hope it'll help you.
Try this one.It's work perfectly for me.
let cellHeight = 100.0
var cell : tableCell?
let detailArr : [UIColor] = [.red,.yellow,.black,.blue]
override func viewWillAppear(_ animated: Bool) {
self.changeTableHeight()
}
func changeTableHeight()
{
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
tableviewHeight.constant = CGFloat(Double(detailArr.count) * self.cellHeight)
self.view.setNeedsLayout()
animator.startAnimation()
self.tableview.reloadData()
}

Animate Constraint Relative to Another One

I setup an animation to hide one switch/label when another one is turned on. Simultaneously the switch that was just turned on move up. This works great with the simple explanation here.
However, when I try to move the switch/label back down after it is turned off it doesn't budge. The other switch reappears fine, but the top constraint change doesn't fire.
I'm relatively new to doing this type of setup and animating all programmatically and after spending an hour on this I'm stumped. Is it because I'm animating a top constraint relative to another one? How does that matter if it works the first time around? Even though the alpha of the hidden switch is set to zero, its frame is still there, right? Or am I doing something simple stupidly?
// Works Perfectly!
func hideVeg() {
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseIn], animations: {
self.vegetarianSwitch.alpha = 0
self.vegetarianLabel.alpha = 0
self.veganSwitch.topAnchor.constraint(equalTo: self.vegetarianSwitch.bottomAnchor, constant: -30).isActive = true
self.view.layoutIfNeeded()
})
}
// Showing the label and switch works, but the topAnchor constraint never changes!
func showVeg() {
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseIn], animations: {
self.vegetarianSwitch.alpha = 1
self.vegetarianLabel.alpha = 1
// This is the constraint that doesn't change.
// This is exactly what it was set to before the other hideVeg() runs.
self.veganSwitch.topAnchor.constraint(equalTo: self.vegetarianSwitch.bottomAnchor, constant: 40).isActive = true
self.view.layoutIfNeeded()
})
}
The issue here is that you are not modifying the constraints but actually creating new constraints with each animation. What you want to do instead is just create the constraint once (you can do it in code or in Interface Builder and drag and outlet). You can then just change the .constant field of the existing constraint in your animation block.
The constant needs to be changed with the animation, not creating an entirely new constraint. The old constraint still exists causing the issue.
var veganTopConstraint = NSLayoutConstraint()
// Top Constraint set up this way so it can be animated later.
veganTopConstraint = veganSwitch.topAnchor.constraint(equalTo: vegetarianSwitch.bottomAnchor, constant: 40)
veganTopConstraint.isActive = true
func hideVeg() {
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseIn], animations: {
self.vegetarianSwitch.alpha = 0
self.vegetarianLabel.alpha = 0
self.veganTopConstraint.constant = -30
self.view.layoutIfNeeded()
})
}
func showVeg() {
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseIn], animations: {
self.vegetarianSwitch.alpha = 1
self.vegetarianLabel.alpha = 1
self.veganTopConstraint.constant = 40
self.view.layoutIfNeeded()
})
}

Constraint is not equal to the height

I write this following code :
if !self.headerIsCollapsed{
self.headerIsCollapsed = true
self.heightProfilView.constant -= (self.view.bounds.height / 5)
UIView.animate(withDuration: 0.3, animations: {
self.imageUserProfil.layer.opacity = 0
self.view.layoutIfNeeded()
})
print(self.heightProfilView.constant)
print(self.topUserProfilView.bounds.height)
}
My question is: Why the value of the two print() is not the same ?
I need the value of self.topUserProfilView.bounds.height Before animate this for another function, would there be another way to catch this value before?
thanks
try this: it should work
if !self.headerIsCollapsed{
self.headerIsCollapsed = true
self.heightProfilView.constant -= (self.view.bounds.height / 5)
UIView.animate(withDuration: 0.3, animations: {
self.imageUserProfil.layer.opacity = 0
self.view.layoutIfNeeded()
}) { (isCompleted) in
print(self.heightProfilView.constant)
print(self.topUserProfilView.bounds.height)
}
}
P.S: This solution would not work if you added the heightProfilView constraint relative to the any view height as here you are changing the constant but in the IB you have assigned the multiplier so in that case the default constant value is 0. So, this approach will not work for the relative height constraint. as we can't change the multiplier of a constraint.
Maybe you have constraints taking priority. Check out your console output when you collapse the row, there should be some loud output involving constraints.
Also try hiding imageUserProfil
self.imageUserProfil.layer.opacity = 0
self.imageUserProfil.isHidden = true
You need completion block to do it...Just replace your animation method as below
UIView.animate(withDuration: 0.3, animations: {
self.imageUserProfil.layer.opacity = 0
self.view.layoutIfNeeded()
}) { (isDone) in
print(self.heightProfilView.constant)
print(self.topUserProfilView.bounds.height)
}
Hope it will work.

iOS Animate height constraint issue

I have a problem with the animation of a view after changing it's height constraint. In the screenshot, you can see it's initial value of 120.0.
The animation works but the constraint update from my second view (the blue one) happens directly and not during the animation. This means that second view jumps to the top directly.
With the following code, I will animate the change of the height constraint:
UIView.animate(withDuration: 3.0, animations: {
self.heightConstraint?.constant = 0.0
self.myLabel.alpha = 0.0
self.layoutIfNeeded()
})
Does anybody know why?
self.heightConstraint?.constant = 0.0
self.myLabel.alpha = 0.0
UIView.animate(withDuration: 3.0, animations: {
self.layoutIfNeeded()
})
It should be like this.
For animating constraint changes, you need to write code like below to work.
self.heightConstraint?.constant = 0.0
self.myLabel.alpha = 0.0
UIView.animate(withDuration: 5) {
self.layoutIfNeeded()
}
You need to call self.layoutIfNeeded() before and after updating constraint constant. Change your code to :
self.layoutIfNeeded()
UIView.animate(withDuration: 3.0, animations: {
self.heightConstraint?.constant = 0.0
self.myLabel.alpha = 0.0
self.layoutIfNeeded()
})

Animate DatePicker hide/show in StackView

How to properly animate datePicker appearance/disappearance in stackView? Currently I tried like this:
UIView.animateWithDuration(0.3, animations: {
self.datePickerView.hidden = !self.datePickerView.hidden
})
This causes problems with hiding animation - it starts nicely and then in the end datePickerView flashes a little bit at the top of where datePicker was. Any suggestions?
I had the same issue and solved it this way:
Put your Picker in a view (we will call it pickerContainerView)
Set a 216 height constraint to your pickerContainerView (picker default height)
Set the constraint priority to 999 to quiet "UISV-hiding" constraint warning
Add "leading", "trailing" and "center vertically" constraints from your picker to the pickerContainerView
animate hide of the pickerContainerView :
Swift 2
UIView.animateWithDuration(0.3, animations: {
self.pickerContainerView.hidden = !self.pickerContainerView.hidden
})
Swift 3, 4, 5
UIView.animate(withDuration: 0.3, animations: {
self.pickerContainerView.isHidden = !self.pickerContainerView.isHidden
})
Using a container to hold the picker and setting clipsToBounds = true worked for me.
I'm using PureLayout, but it should work with IB too.
startRangePickerContainer = UIView()
startRangePickerContainer.clipsToBounds = true
startRangePickerContainer.backgroundColor = UIColor.cyan
stackView.addArrangedSubview(startRangePickerContainer)
startRangePickerContainer.autoPinEdge(toSuperviewEdge: .leading)
startRangePickerContainer.autoSetDimension(.height, toSize: 216)
startRangePickerContainer.autoPinEdge(toSuperviewEdge: .leading)
startRangePickerContainer.autoPinEdge(toSuperviewEdge: .trailing)
startRangePicker = UIDatePicker()
startRangePickerContainer.addSubview(startRangePicker)
startRangePicker.autoCenterInSuperview()
To animate:
UIView.animate(withDuration: 0.3, animations: {
self.startRangePickerContainer.isHidden = !self.startRangePickerContainer.isHidden
})

Resources