How to Animate Constraint Changes

How do I animate constraint changes?

Two important notes:

  1. You need to call layoutIfNeeded within the animation block. Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed

  2. You need to call it specifically on the parent view (e.g. self.view), not the child view that has the constraints attached to it. Doing so will update all constrained views, including animating other views that might be constrained to the view that you changed the constraint of (e.g. View B is attached to the bottom of View A and you just changed View A's top offset and you want View B to animate with it)

Try this:

Objective-C

- (void)moveBannerOffScreen {
[self.view layoutIfNeeded];

[UIView animateWithDuration:5
animations:^{
self._addBannerDistanceFromBottomConstraint.constant = -32;
[self.view layoutIfNeeded]; // Called on parent view
}];
bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
[self.view layoutIfNeeded];

[UIView animateWithDuration:5
animations:^{
self._addBannerDistanceFromBottomConstraint.constant = 0;
[self.view layoutIfNeeded]; // Called on parent view
}];
bannerIsVisible = TRUE;
}

Swift 3

UIView.animate(withDuration: 5) {
self._addBannerDistanceFromBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}

Animate subview AutoLayout constraint changes

The animation block should be in the UIView that contains the 3 MySubviews. Inside the MySubview class you only update the height constraint's constant:

In Subview

func toggleHeight() {
let newHeight = isVisibleInContext ? 30 : 0
heightConstraint.constant = newHeight
}

Then in the UIView that contains the 3 MySubviews you animate the change:

In Superview

func toggleHeight(with subview: MySubview) {
subview.toggleHeight()
UIView.animate(withDuration: 0.8) {
self.view.layoutIfNeeded()
}
}

Sample Image

Swift - Animate Constraints in Custom UIView

The reason why your initial post is not working is that you called your first layoutIfNeeded() after the constraint changes. When your animation block is executed, there's no changes to be animated at all.

It does not matter whether you change the constraints in or out of the animation block.

The common steps are:

  1. Call layoutIfNeeded() on the rootest view that your constraints may affect. In your case, self is ok because all the involving constraints are for the subviews. In some cases, you might change a constraint for the self view, then you need to call layoutIfNeeded on its superview.
  2. Make your constraint changes: modify constant property, activate/deactivate constraints, etc.
  3. Call layoutIfNeeded() in a animation block on the same view as you did in the first step.

Please try the following alternative:

func playAnimation() {
// Request a layout to clean pending changes for the view if there's any.
self.layoutIfNeeded()

// Make your constraint changes.
listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)

UIView.animate(withDuration: 3.0) {
// Let your constraint changes update right now.
self.layoutIfNeeded()
}
}

UIKit: how to animate a constraint change with a programmatically created constraint?

One approach:

  • create one constraint setting the Bottom of customTopView above the Top of the view
  • create a second constraint setting the Top of customTopView below the Top of the view
  • add customTopView as a subview, activating the first constraint
  • animate customTopView into view by deactivating the first constraint and activating the second constraint

Here's a modified version of your showCustomTopView(...) extension, using a UILabel as customTopView -- should work fine with your CustomTopView.instance(withTitle: ...):

extension UIView {
func showCustomTopView(withTitle title: NSAttributedString, subtitle: NSAttributedString) {
let customTopView = UILabel()
customTopView.text = title.string
customTopView.backgroundColor = .green
addSubview(customTopView)
customTopView.translatesAutoresizingMaskIntoConstraints = false
let g = self.safeAreaLayoutGuide
// constraint: Bottom of customTopView to Top of self Minus 8-pts
let cvBot = customTopView.bottomAnchor.constraint(equalTo: topAnchor, constant: -8.0)
// constraint: Top of customTopView to Top of self Plus 8-pts
let cvTop = customTopView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0)
NSLayoutConstraint.activate([
customTopView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
customTopView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
// activate cvBot, so customTopView is 8-pts above the top of self
cvBot,
])
// execute this async, so the initial view position is set
DispatchQueue.main.async {
// deactivate the Bottom constraint
cvBot.isActive = false
// activate the Top constraint
cvTop.isActive = true
// animate it into view
UIView.animate(withDuration: 0.5, animations: {
self.layoutIfNeeded()
})
}
}
}

Animate constraints change UIViewController

You're trying to animate constraints in wrong way. You should set constraints outside of animation block and only layoutIfNeeded in animation:

func showError(_ text: String){
botAnchor.isActive = false
topAnchor.isActive = true
error.show(text)
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}

Animate specific constraint changes by layoutIfNeeded()

The problem is that your first animations change their view’s frame, as in self.bannerTwoItemContainer.frame.origin.x. But you cannot change the frame of something that is positioned by constraints. If you do, then as soon as layout occurs, the constraints reassert themselves. That is exactly what you are doing and exactly what happens. You say layoutIfNeeded and the constraints, which you never changed, are obeyed.

Rewrite your first animations to change their view’s constraints, not their frame, and all will be well.

Width constraint change with UIView animation

I achieved desired result by changing view.center.x . I tried to change leading constraint and width but it didn't work out. I also happen to change width of my view but it happens without animation and before/after for expand/collapse respectively. So here is my code:

self.snp.updateConstraints({(make) in
make.width.equalTo(150.0)
})

UIView.animate(withDuration: 0.2,
animations: {
self.center.x -= 90
self.layoutIfNeeded()
}, completion: nil)

And reverse :

UIView.animate(withDuration: 0.2,
animations: {
self.center.x += 90
self.layoutIfNeeded()
}, completion: nil)

self.snp.updateConstraints({(make) in
make.width.equalTo(30.0)
})

Animation is a bit rough when it comes to collapsing but works just fine for expanding. Still appreciate any suggestion.

How to animate a UIImage constraints in Swift 4

First of all you have to drag and drop your constraint into your view controller, to make an outlet. The same way you are doing it with UIImageView or UITableView, etc.

Actually, for those who have watched WWDC (2014 I guess), Apple have explained how to animate UI in the proper way.

Firstly, you have to call layoutIfNeeded() method to make sure that everything on your view have laid out. After you can change your constraint and right after that in your animation block you call layoutIfNeeded() method to layout your constraint with the new value.

So code should look like that:

view.layoutIfNeeded() // to make sure your view have laid out

self.constraint.constant = //change your constant to any value

UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded() //layout your constraint with the new value
}

How to animate the nested view on constraint layout?

Remember, ConstraintSet Animation works as any other animation with two stages - Animation Start and Animation End. So, First set the view constraint as Animation start which means hidden under bottom of the layout.

So, Change the initial constraints as:

<!-- Here app:layout_constraintTop_toBottomOf will position the view under the layout/screen -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/error_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="40dp"
android:background="#fff"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

Then, create the constraints as

//rootLayout is the Id of the parent layout
val constraintSet = ConstraintSet()
constraintSet.clone(rootLayout)
constraintSet.clear(error_layout.id, ConstraintSet.TOP)
constraintSet.connect(error_layout.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
val transition: Transition = ChangeBounds()
transition.interpolator = AnticipateOvershootInterpolator(1.0f)
transition.duration = 1200
TransitionManager.beginDelayedTransition(rootLayout, transition)
constraintSet.applyTo(rootLayout)

Then, if you want to hide it again. Just replace the TOP and BOTTOM as

constraintSet.clear(error_layout.id, ConstraintSet.BOTTOM)
constraintSet.connect(error_layout.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)

Sample Image

GIF took more time than coding :p.

EDIT - To listen to animation updates, especially Animation end as requested, what you can do is you can set a listener on transition as

transition.addListener(object : Transition.TransitionListener {
override fun onTransitionStart(transition: Transition) {
}
override fun onTransitionEnd(transition: Transition) {
}
override fun onTransitionCancel(transition: Transition) {
}
override fun onTransitionPause(transition: Transition) {
}
override fun onTransitionResume(transition: Transition) {
}
})

Put it just before the beginDelayedTransition().



Related Topics



Leave a reply



Submit