how to animate dependent constraints - ios

I'm trying to animate 2 constraints(which determine position on y axis and height) of the same view at same time. The problem is, that they are dependent, by which I mean, they work together to determine the same property. The picture shows what I mean.
2 arrows depict constraints. The upper one sets the distance between upper view and bottom view, the bottom constrains sets the distance between bottom layout guide and bottom view. The bottom view slides from below the view and stops at the position you are currently seeing. The height is set with animating constraints. This is the code I'm using to make animation:
self.toBottomConstraint.constant = 53
self.toUpperConstraint.constant = 15
UIView.animateWithDuration(0.2, delay: 0, usingSpringWithDamping: 20, initialSpringVelocity: 20, options: UIViewAnimationOptions.CurveLinear, animations: {
self.view.layoutIfNeeded()
}, completion: {_ in})
The code actually works but I'm getting this error.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) ...
....
....
....
So how should I animate 2 dependent constraints?
EDIT: //upper view constraints.
// bottom view constraints. BuyButton and collection view are nested inside bottom view

Depending on how you compose your animation, there're literally infinite ways to set the constraints before and after the animation. Most importantly, you need to decide what the constraints were, before the animation, and what they will be after the animation. For example:
self.toBottomConstraint.constant = 153
self.toUpperConstraint.constant = 0
UIView.animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
self.toBottomConstraint.constant = 53
self.toUpperConstraint.constant = 15
self.view.layoutIfNeeded()
}, completion: nil)
The code above will animate the bottom view bottom up.
By the way, the console warning message for conflict constraints might be caused by other constraint you set on the bottom view apart from its top and bottom constraints, for example, you might have configured a height constraint for the bottom view, in which case, you need to update the height constraint after the animation.

You're almost there, just move the changing of the constants into the closure and call layoutIfNeeded() earlier too:
self.layoutIfNeeded()
UIView.animateWithDuration(0.2, delay: 0, usingSpringWithDamping: 20, initialSpringVelocity: 20, options: UIViewAnimationOptions.CurveLinear, animations: {
self.toBottomConstraint.constant = 53
self.toUpperConstraint.constant = 15
self.view.layoutIfNeeded()
}, completion: {_ in})
You can read more about animating constraints in another answer about constraint changes.

Related

Why do these constraints seem to lag when animated?

I am animating my constraints using UIView.animate. drawerHeight is a constraint variable.
Here is the code:
drawerHeight?.constant = newHeight
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 2, options: .allowUserInteraction, animations: {
self.superview?.layoutIfNeeded()
})
Here is a video of what the issue looks like.
Looking at the bottom of the video, the bottom edge appears to lag behind. Why do these constraints seem to lag when animated?
This is a self-answered question. This post is to help people in the future discover what they did wrong, and how to quickly fix it.
The issue was that the constraint was attached to a view in a deeper hierarchy than before. When using layoutIfNeeded(), it should be called on the superview of the view containing the constraints.
In my situation, I put the view with the drawerHeight constraint within another UIView, which made it deeper in the hierarchy.
I fixed the issue by doing
self.superview?.superview?.layoutIfNeeded() ,
instead of
self.superview?.layoutIfNeeded() .

Change width UIView by animation and center

I have a view and need change width from 100 to 200 by animation.
UIView.animate(withDuration: 5
,animations: {
self.backgroundPlaceHolderView.frame.size.width += 200
})
when running this code, my view change width, but I need a change in width from the center, but this code increase view to the right
If you really need to do it with frames, here's a code snippet:
UIView.animate(withDuration: 1, animations: {
self.backgroudPlaceHolderView.frame.origin.x -= 100
self.backgroudPlaceHolderView.frame.size.width += 200
})
If you moved from manipulating the frame to manipulating constraints this would be easier and cleaner in my opinion.
In the InterfaceBuilder (or programmatically) center your UIView horizontally.
Outlet your width constraint (which would begin at 100).
When you want to increase the width of your UIView you can do the following;
view.layoutIfNeeded() // always make sure any pending layout changes have happened
widthConstraint.constant = 200
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
Autolayout will take care of the rest for you. Your view is centered horizontally so it should expand from the center.
You should have that backgroundPlaceHolderView constrained on the centerX. It's probably anchored on the left.

UIView constraints won't animate if layoutIfNeeded() is called

I'm trying to animate a view that is constrained (iconVerticalConstraint) to be at center Y (+100) of a superview, specifically reaching its right (0) position after an animation.
Here's my code:
self.view.layoutIfNeeded()
UIView.animateKeyframes(withDuration: 2, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations:{
self.iconVerticalConstraint.constant -= 20
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.75, animations:{
self.iconVerticalConstraint.constant -= 80
})
})
I tried following this question's answer but that didn't help, because in my case my view contains more subviews, intrinsically linked, and putting layoutIfNeeded() in my animation block would animate the whole view, and not just that constraint. Otherwise this approach does no animation at all.
changing constraints such as
self.iconVerticalConstraint.constant -= 20
should be OUTSIDE the animation block and inside you just call
self.view.layoutIfNeeded()
I think because you are trying to do 2, and calling
self.view.layoutIfNeeded()
They all happen once, consider changing to:
self.iconVerticalConstraint.constant -= 20
UIView.animate(withDuration: 0.25, animations: {
self.view.layoutIfNeeded()
}) { (finished) in
self.iconVerticalConstraint.constant -= 80
UIView.animate(withDuration: 0.75, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Edit: Explanation
When you change a constraint, the view will not update until the next layout cycle, usually when you exit your current scope. So when you want to animate a constraint, you change the constant BEFORE the animation block, then inside the animation block you call layoutIfNeeded() This tells it to animate the now forced layout cycle.
Now in your example, you where calling layoutIfNeeded() which would do nothing at that point, then changing 2 constants, without telling the layout to animate, so when the scope exited, the UI would layout without animation.
In your case you want to chain the changing of 2 constants, but you dont have a way of changing the second constant (80) after the first animation completes, and if you set both, both constants will be adjusted on the FIRST layoutIfNeeded() hence, the chaining of animations, which will still work with your other animations that you omitted, just add then in the first animation block if they are in the first keyframe and the second if they were in the second.
Final Edit:
Also you can change your layoutIfNeeded() currently applied to self.view to actually on the views it affects, in this case iconVerticalConstraint if it was a view that is attached to another view say a top attached to a view bottom, if you wanna animate, you must call layoutIfNeeded() on each view it affects
After hours of time spent to understand the basic mechanism, I can provide my own answer, for all of you who will find themselves in my situation.
layoutIfNeeded() tells to the view to adjust position of all of its subviews.
animate or animateKeyframes or every other alternative you're using, instead, tells to the view to perform any of the instructions inside the animation block in the way you specify in the animate function (by default, with a options: .easeInOut)
Back to the question: the problem was in the fact that animate and layoutIfNeeded are called asynchronously so that, in fact, every instruction inside the animation block was executed just before the layoutIfNeeded instruction, making it useless. The solution isn't putting the layoutIfNeeded instruction inside the animation block, as suggested by SeanLintern88 because in this way we would tell the view to adjust the position of its subviews according to the animation options. That's the key point.
The answer is somewhat simple. Move the animation part on the subview itself, specifically in a point when you know that the superview has already been "autolayouted" (e.g. viewDidAppear), so that the constraint is already defined and executed, and you can now change it. In that point, code should look like this:
UIView.animateKeyframes(withDuration: 2, delay: 0, animations: {
self.view.layoutIfNeeded()
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations:{
self.iconVerticalConstraint.constant -= 20
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.75, animations:{
self.iconVerticalConstraint.constant -= 80
})
})
Because you want to animate the autolayout.
Another option (for instance, if you haven't/don't want to create a class for your subview) is to change bounds/center of the subview itself in the viewDidAppear function of the parent view. This is because, if you are in the parent view, you know that at that point auto layout has already been registered but not executed, in the more precise sense that bounds and center have been already changed according to the auto layout.
If you then change them, you'll obtain your desired animation (but remember: auto layout is still on, so you must change the constraints in a way that reflects the final position of your animation; if you wish, you can do that in the completion part of animate function).

Button animation doesn't work properly in swift

I'm making an app in swift using Xcode and I'm having troubles with the animation of some button.
Here's my situation:
I have three buttons in the middle of the screen where i've also added some constraints.
What i need is that when the button is clicked it reaches the top of the screen, like in this second photo with an animation:
I've tried this:
UIView.animateWithDuration(1, delay: 0, options: [], animations: {
self.Button2.center.y = 18 + self.Button2.frame.height/2
}, completion: nil)
But what happens is that the button appears from the bottom of the screen slides up to the position of the first photo. It doesn't respect the position I wrote in the animation. Is it possibile that this is due to the constraints? Because I centered it vertically and horizontally.
Can you help me to make it work?
When you work with autolayout and constraints you never make changes to the button frame in your animation, like your code: you must work always with constraints.
This is a generic example:
myButtonCenterYConstraint.constant = 200.0
myButton.layoutIfNeeded()
UIView.animateWithDuration(Double(0.5), animations: {
myButtonCenterYConstraint.constant = 0
myButton.layoutIfNeeded()
})
So, the first step is to declare the button constraints you will want to change as IBOutlets:
First get the center y of the button then reposition the button.
float centerY = self.Button2.center.y;
UIView.animateWithDuration(1, delay: 0, options: [], animations: {
self.Button2.center.y = centerY + 18 + self.Button2.frame.height/2
}, completion: nil)
You must change the constraint first and then update the UI in animation. Something like this.
yourButton.yourOriginConstraint'sYCoordinate.constant = // Set this to what you want to change.
// yes, you should create an IBOutlet for constraint that you want to modify.
UIView.animateWithDuration(1.0, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
Another way is to not user auto layout.
Here, is the reference

Core animation (size change) with autolayout challenge

I have a piece of core animation by use of 2 methods in order to give appearance that the object is growing in size from the centre-outwards from the existing location...
(1) viewDidLayoutSubviews()
self.imageView.frame = CGRectMake(187, 249, 0, 0)
which is in the centre of the screen for this imageView object
(2) viewDidAppear()
- with animation block of duration 1 second. e.g.
UIView.animateWithDuration(1.0, delay: 1.5, options: [], animations: {
self.imageView.frame = CGRectMake(16, 70, 343, 358)
}, completion: nil)
which puts object where I want it to show in full size, for the specific screen size.
When I use the x and y coordinates for CGRectMake(...) on the iPhone size I'm looking for they work fine, But when I try on simulator with different screen sizes, it doesn't work in conjunction with the Autolayout constraints, but overrides the existing Constraints.
Question(s)...
Is there a way of making this animation work by centering it proportional to the screen size somehow, rather than explicit coordinates and sizes? via CGRectMake...? (...which seems to show a clash between use of Autolayout and Animation)
Ideally I'm looking for an elegant simple solution? or am I at the limits of the tech?
Many thanks in advance!
You can set imageView location using autolayout constraints & give it a size constraint and by taking an outlet from it you can animate height to increase leaving all other constraints working
#IBOutlet var myViewHeight: NSLayoutConstraint!
UIView.animateWithDuration(0.3, delay: 0.0, options: [], animations:
{
() -> Void in
self.myViewHeight.constant = 300
self.view.layoutIfNeeded()
}, completion:nil)
You should take the outlet from the constraint itself like control+drag on Label.top = topMargin

Resources