How do I animate constraint changes?
Two important notes:
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 completedYou 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()
}
}
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:
- 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 calllayoutIfNeeded
on its superview. - Make your constraint changes: modify constant property, activate/deactivate constraints, etc.
- 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)
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
Swift - Json Error: the Data Couldn'T Be Read Because It Isn'T in the Correct Format
How to Increment the Filename If File Already Exists
How to Load an Http Url With App Transport Security Enabled in iOS 9
How to Export Uiimage Array as a Movie
How to Change the Push and Pop Animations in a Navigation Based App
Move View With Keyboard Using Swift
Making a Uitableview Scroll When Text Field Is Selected
Swift: How to Split Up a [String] Resulting in a [[String]] With a Given Subarray Size
How to Find Current Visible Viewcontroller in Ios
How to Input Currency Format on a Text Field (From Right to Left) Using Swift
Applications Are Expected to Have a Root View Controller At the End of Application Launch
How to Create a Basic Uibutton Programmatically