Should I enclose "addArrangedSubview" to the animation block or not? - ios

I'm not learning the use of UIStackView and read a good tutorial on the web. In the tutorial, the author writes the following code to make an animation:
#IBAction func addStar(sender: AnyObject) {
let starImgVw:UIImageView = UIImageView(image: UIImage(named: "star"))
starImgVw.contentMode = .ScaleAspectFit
self.horizontalStackView.addArrangedSubview(starImgVw)
UIView.animateWithDuration(0.25, animations: {
self.horizontalStackView.layoutIfNeeded()
})
}
However, when I cloned the repository and changed the code slightly, I still saw the same animation properly.
#IBAction func addStar(sender: AnyObject) {
let starImgVw:UIImageView = UIImageView(image: UIImage(named: "star"))
starImgVw.contentMode = .ScaleAspectFit
UIView.animateWithDuration(0.25, animations: {
self.horizontalStackView.addArrangedSubview(starImgVw)
self.horizontalStackView.layoutIfNeeded()
})
}
I moved self.horizontalStackView.addArrangedSubview(starImgVw) to the inner part of the animation block.
I also tried the same thing on removeStar function; this time moved both self.horizontalStackView.removeArrangedSubview(aStar) and aStar.removeFromSuperview(), but I also confirmed the animation work properly.
So my question is the following:
Which is the better way?
Why do these two code work in the same way?
When I removed layoutIfNeeded(), then the animation didn't work. This is because if I don't force the views to be updated immediately, then the next view update cycle occurs after the animation block and thus the animation is no longer valid, right?

In an animation block, you just want to include the change that you want to see animated. You shouldn't include several changes at the same time because then the functionality becomes a bit unpredictable. You won't be sure which change will take precedence over the others.
So to answer your question, the first example you have with
UIView.animateWithDuration(0.25, animations: {
self.horizontalStackView.layoutIfNeeded()
})
is the better way to write this piece of code.
Only specific properties of UIView are animatable. From Apple's docs:
The following properties of the UIView class are animatable:
#property frame
#property bounds
#property center
#property transform
#property alpha
#property backgroundColor
#property contentStretch
Essentially, by calling layoutIfNeeded, you are allowing animateWithDuration to animate the adding of constraints to the star view before it's laid out by the processor. That's why you see it move to the right.
Removing layoutIfNeeded() would just leave your adding a subview function. Adding a subview is not animatable with the animateWithDuration function. That's why it didn't work. You can make it look animated by setting alpha to 0.0 when you first create it and then in animateWithDuration set alpha to 1.0.
starImgVw.alpha = 0.0
horizontalStackView.addArrangedSubview(starImgVw)
UIView.animateWithDuration(0.25) { () -> Void in
starImgVw.alpha = 1.0
}
I hope that answers your questions fully.

Related

I put UIView.animateWithDuration in viewWillAppear but it runs only during first entrance to the panel - why?

I try to animate a background in my swift ios app. I have an UIImageView with UIImage and I have the following method:
func slideImage(){
// Changes constant to be equal to the image width,
// this will move the image off-screen on the left-hand side.
backgroundImageConstraint.constant = backgroundPhoto.size.width
UIView.animateWithDuration(5, delay: 0, options: [.CurveLinear, .Repeat], animations: {
// Animates the constant change
self.view.layoutIfNeeded()
}, completion: {
done in
// Resets the image view back to its original position before starting a new round of the animation
self.backgroundImageConstraint.constant = 0
self.view.setNeedsUpdateConstraints()
})
}
I call this method in the viewWillAppear function, so I expected to run it every time when user enters my panel. However that's not how it works here - I see that it is invoked only once, when I enter the panel for the 2nd time the image in the background is not scrolling. This is a weird behavior. In my viewDidLoad I set all the things related to the UIImage:
override func viewDidLoad() {
super.viewDidLoad()
backgroundImage.image = backgroundPhoto
backgroundImage.frame = CGRect(x: backgroundImage.frame.origin.x, y: backgroundImage.frame.origin.y, width: backgroundPhoto.size.width, height: backgroundPhoto.size.height)
backgroundImage.contentMode = .Center
}
Does anyone knows why my photo is sliding only when user enters the panel for the first time and in other cases it is a static image?
Okay I'm gonna answer my own question - I found the information somewhere that if something interrupts the animations, the closure gets called automatically. So I moved calling the function slideImage() from viewWillAppear to viewDidAppear and now it works like a charm.
The animateWithDuration method is changing the frame of your background image moving it from the frame you set in viewDidLoad to the frame you have set (probably in your storyboard, with constraints). After your backgroundImage has been animated, it's frame changes and the next time you execute slideImage() method, the frame will be already changed, so it won't animate.
Try putting this two lines
backgroundImage.frame = CGRect(x: backgroundImage.frame.origin.x, y: backgroundImage.frame.origin.y, width: backgroundPhoto.size.width, height: backgroundPhoto.size.height)
backgroundImage.contentMode = .Center
before calling slideImage() in viewWillAppear and let us know the result.

Choppy animation in iOS

I have an animation in iOS with the following code:
view.layoutIfNeeded()
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
// constraints which are unrelated to drawingResultsController
self.leadingSpaceToContainerViewConstraint.constant = 0
self.trailingSpaceToContainerViewConstraint.constant = 0
// This controller doesn't have any constraints
drawingResultsController.view.frame.origin = CGPoint(x: self.view.bounds.width, y: 0)
self.view.layoutIfNeeded()
}, completion: { finished in
drawingResultsController.view.removeFromSuperview()
drawingResultsController.removeFromParentViewController()
drawingResultsController.didMoveToParentViewController(nil)
})
however, the animation does not look very fluid. It's not terrible by any means but you can definitely tell it is way under 30 fps. Does anybody have any idea as to why could this be? I have already verified everything is on the main thread.
Extra details:
The parent controller's view has a subview which is pinned to the edges of it's superview (the main view), those are the constraints seen above. This subview contains the main views of the controller (i.e. it's there to create a parallax effect as the drawingResultsController slides in and out of the screen). drawingResultsController is a child view controller added as a subview of the main view, and it doesn't have any constraints. It slides in and out of the screen, and this is the code to slide it out.
This way, the view moved by the constraints is a sibling of the view of the drawingResultsController. Both are direct subviews of the main view.
EDIT:
Reducing the animation to just this
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
drawingResultsController.view.frame.origin = CGPoint(x: self.view.bounds.width, y: 0)
}, completion: nil)
improves the frame-rate a little, but it continues to be an issue. It is more noticeable when setting longer animation times. The usage of CPU and memory look completely normal at the time of the animation.
Thank you
I had a similar issue, turns out it is solved by rebooting the device.
However the issue also seems to be solved by having a CADisplayLink updating a random layer that's part of your window's hierarchy. I noticed that, because we had an animation running somewhere in our app that was triggered by a CADisplayLink and that solved all slow animations elsewhere in the app!
The hack below solves the issue. Add the code to your main window class (assuming you use a custom UIWindow sub class for your main window):
//Hack for slow animation problem that occurs after long uptime of the device, updating a view's layer position that's in the view hierarchy using a display link solves the slow animations.
#property (nonatomic, strong) UIView *dummyView;
#property (nonatomic, strong) CADisplayLink *displayLink;
- (void)enableSlowAnimationHack {
if (self.dummyView == nil) {
self.dummyView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:self.dummyView];
}
if (self.displayLink == nil) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animateWithLink:)];
[self.displayLink addToRunLoop: [NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)animateWithLink:(CADisplayLink *)sender {
self.dummyView.layer.position = CGPointMake((arc4random() % 100) / 100.0, (arc4random() % 100) / 100.0);
}
Apparently it solved on its own. It doesn't make much sense since it happened in both the simulator and the device, and no code was changed.

Is there a way to programmatically update frames (Swift)

Is it possible to update the frames like in the interface builder but programmatically? Some of my objects get misplaced because of an animation (which I'll want to fix anyway, I guess..) but it brought to mind the aforementioned question
Edit: I have done some googling as well as looking on stackoverflow and not found what I'm looking for.
I want to update the frames of some buttons back to the constraints I set in the interface builder.
Sorry if this is too simple of a question, but I'm just looking for a simple answer: yes or no + line of code or the method to call. it probably won't even end up in my final project; I'm just curious.
Here's some code, since I guess that would help:
#IBAction func optionsButtonPushed(sender: AnyObject) {
UIView.animateWithDuration(2, animations: {
var theFrame = self.optionsMenu.frame
theFrame.size.height += 100
self.optionsMenu.frame = theFrame
})
}
The buttons located in the view I'm rolling in (from a height of zero) disappear because of the animation, I guess, so there's probably a better way than what I'm thinking
(sorry this question is so crazy; it's my first one!)
You can disable auto-layout. from storyboard. IF you still want to use AutoLayout then use -viewDidLayoutSubviews method.
self.yourButton.translatesAutoresizingMaskIntoConstraints = YES;
self.yourButton.frame = CGRectMake(16, 133, 130, 130);
UIView has a property called frame which is of type CGRect. CGRect is a struct.
You can update it by creating a new CGRect and assigning it to the property.
CGRect newFrame = CGRect(x:0, y:0, width:100, height:100)
yourView.frame = newFrame
UIView Documentation

How do I animate constraint changes?

I'm updating an old app with an AdBannerView and when there is no ad, it slides off screen. When there is an ad it slides on the screen. Basic stuff.
Old style, I set the frame in an animation block.
New style, I have a IBOutlet to the auto-layout constraint which determines the Y position, in this case it's distance from the bottom of the superview, and modify the constant:
- (void)moveBannerOffScreen {
[UIView animateWithDuration:5 animations:^{
_addBannerDistanceFromBottomConstraint.constant = -32;
}];
bannerIsVisible = FALSE;
}
- (void)moveBannerOnScreen {
[UIView animateWithDuration:5 animations:^{
_addBannerDistanceFromBottomConstraint.constant = 0;
}];
bannerIsVisible = TRUE;
}
And the banner moves, exactly as expected, but no animation.
UPDATE: I re-watched WWDC 12 talk Best Practices for Mastering Auto Layout which covers animation. It discusses how to update constraints using CoreAnimation:
I've tried with the following code, but get the exact same results:
- (void)moveBannerOffScreen {
_addBannerDistanceFromBottomConstraint.constant = -32;
[UIView animateWithDuration:2 animations:^{
[self.view setNeedsLayout];
}];
bannerIsVisible = FALSE;
}
- (void)moveBannerOnScreen {
_addBannerDistanceFromBottomConstraint.constant = 0;
[UIView animateWithDuration:2 animations:^{
[self.view setNeedsLayout];
}];
bannerIsVisible = TRUE;
}
On a side note, I have checked numerous times and this is being executed on the main thread.
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 completed
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()
}
I appreciate the answer provided, but I think it would be nice to take it a bit further.
The basic block animation from the documentation
[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];
but really this is a very simplistic scenario. What if I want to animate subview constraints via the updateConstraints method?
An animation block that calls the subviews updateConstraints method
[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
[self.view layoutIfNeeded];
} completion:nil];
The updateConstraints method is overridden in the UIView subclass and must call super at the end of the method.
- (void)updateConstraints
{
// Update some constraints
[super updateConstraints];
}
The AutoLayout Guide leaves much to be desired but it is worth reading. I myself am using this as part of a UISwitch that toggles a subview with a pair of UITextFields with a simple and subtle collapse animation (0.2 seconds long). The constraints for the subview are being handled in the UIView subclasses updateConstraints methods as described above.
Generally, you just need to update constraints and call layoutIfNeeded inside the animation block. This can be either changing the .constant property of an NSLayoutConstraint, adding remove constraints (iOS 7), or changing the .active property of constraints (iOS 8 & 9).
Sample Code:
[UIView animateWithDuration:0.3 animations:^{
// Move to right
self.leadingConstraint.active = false;
self.trailingConstraint.active = true;
// Move to bottom
self.topConstraint.active = false;
self.bottomConstraint.active = true;
// Make the animation happen
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
Sample Setup:
Controversy
There are some questions about whether the constraint should be changed before the animation block, or inside it (see previous answers).
The following is a Twitter conversation between Martin Pilkington who teaches iOS, and Ken Ferry who wrote Auto Layout. Ken explains that though changing constants outside of the animation block may currently work, it's not safe and they should really be change inside the animation block.
https://twitter.com/kongtomorrow/status/440627401018466305
Animation:
Sample Project
Here's a simple project showing how a view can be animated. It's using Objective C and animates the view by changing the .active property of several constraints.
https://github.com/shepting/SampleAutoLayoutAnimation
// Step 1, update your constraint
self.myOutletToConstraint.constant = 50; // New height (for example)
// Step 2, trigger animation
[UIView animateWithDuration:2.0 animations:^{
// Step 3, call layoutIfNeeded on your animated view's parent
[self.view layoutIfNeeded];
}];
Swift 4 solution
UIView.animate
Three simple steps:
Change the constraints, e.g.:
heightAnchor.constant = 50
Tell the containing view that its layout is dirty and that the autolayout should recalculate the layout:
self.view.setNeedsLayout()
In animation block tell the layout to recalculate the layout, which is equivalent of setting the frames directly (in this case the autolayout will set the frames):
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Complete simplest example:
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Sidenote
There is an optional 0th step - before changing the constraints you might want to call self.view.layoutIfNeeded() to make sure that the starting point for the animation is from the state with old constraints applied (in case there were some other constraints changes that should not be included in animation):
otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
UIViewPropertyAnimator
Since with iOS 10 we got a new animating mechanism - UIViewPropertyAnimator, we should know that basically the same mechanism applies to it. The steps are basically the same:
heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
animator.startAnimation()
Since animator is an encapsulation of the animation, we can keep reference to it and call it later. However, since in the animation block we just tell the autolayout to recalculate the frames, we have to change the constraints before calling startAnimation. Therefore something like this is possible:
// 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
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()
The order of changing constraints and starting an animator is important - if we just change the constraints and leave our animator for some later point, the next redraw cycle can invoke autolayout recalculation and the change will not be animated.
Also, remember that a single animator is non-reusable - once you run it, you cannot "rerun" it. So I guess there is not really a good reason to keep the animator around, unless we use it for controlling an interactive animation.
Swift solution:
yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
yourView.layoutIfNeeded
})
Storyboard, Code, Tips and a few Gotchas
The other answers are just fine but this one highlights a few fairly important gotchas of animating constraints using a recent example. I went through a lot of variations before I realized the following:
Make the constraints you want to target into Class variables to hold a strong reference. In Swift I used lazy variables:
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
return temp!
}()
After some experimentation I noted that one MUST obtain the constraint from the view ABOVE (aka the superview) the two views where the constraint is defined. In the example below (both MNGStarRating and UIWebView are the two types of items I am creating a constraint between, and they are subviews within self.view).
Filter Chaining
I take advantage of Swift's filter method to separate the desired constraint that will serve as the inflection point. One could also get much more complicated but filter does a nice job here.
Animating Constraints Using Swift
Nota Bene - This example is the storyboard/code solution and assumes
one has made default constraints in the storyboard. One can then
animate the changes using code.
Assuming you create a property to filter with accurate criteria and get to a specific inflection point for your animation (of course you could also filter for an array and loop through if you need multiple constraints):
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
return temp!
}()
....
Sometime later...
#IBAction func toggleRatingView (sender:AnyObject){
let aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)
self.view.layoutIfNeeded()
//Use any animation you want, I like the bounce in springVelocity...
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.75, options: [.CurveEaseOut], animations: { () -> Void in
//I use the frames to determine if the view is on-screen
if CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away
//I play a sound to give the animation some life
self.centerYInflection.constant = aPointAboveScene
self.centerYInflection.priority = UILayoutPriority(950)
} else {
//I play a different sound just to keep the user engaged
//out of frame ~ animate into scene
self.centerYInflection.constant = 0
self.centerYInflection.priority = UILayoutPriority(950)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}) { (success) -> Void in
//do something else
}
}
}
The many wrong turns
These notes are really a set of tips that I wrote for myself. I did all the don'ts personally and painfully. Hopefully this guide can spare others.
Watch out for zPositioning. Sometimes when nothing is apparently
happening, you should hide some of the other views or use the view
debugger to locate your animated view. I've even found cases where a User Defined Runtime
Attribute was lost in a storyboard's xml and led to the animated
view being covered (while working).
Always take a minute to read the documentation (new and old), Quick
Help, and headers. Apple keeps making a lot of changes to better
manage AutoLayout constraints (see stack views). Or at least the AutoLayout Cookbook. Keep in mind that sometimes the best solutions are in the older documentation/videos.
Play around with the values in the animation and consider using
other animateWithDuration variants.
Don't hardcode specific layout values as criteria for determining
changes to other constants, instead use values that allow you to
determine the location of the view. CGRectContainsRect is one
example
If needed, don't hesitate to use the layout margins associated with
a view participating in the constraint definition
let viewMargins = self.webview.layoutMarginsGuide: is on example
Don't do work you don't have to do, all views with constraints on the
storyboard have constraints attached to the property
self.viewName.constraints
Change your priorities for any constraints to less than 1000. I set
mine to 250 (low) or 750 (high) on the storyboard; (if you try to change a 1000 priority to anything in code then the app will crash because 1000 is required)
Consider not immediately trying to use activateConstraints and
deactivateConstraints (they have their place but when just learning or if you are using a storyboard using these probably means your doing too much ~ they do have a place though as seen below)
Consider not using addConstraints / removeConstraints unless you are
really adding a new constraint in code. I found that most times I
layout the views in the storyboard with desired constraints (placing
the view offscreen), then in code, I animate the constraints previously created in the storyboard to move the view around.
I spent a lot of wasted time building up constraints with the new
NSAnchorLayout class and subclasses. These work just fine but it
took me a while to realize that all the constraints that I needed
already existed in the storyboard. If you build constraints in code
then most certainly use this method to aggregate your constraints:
Quick Sample Of Solutions to AVOID when using Storyboards
private var _nc:[NSLayoutConstraint] = []
lazy var newConstraints:[NSLayoutConstraint] = {
if !(self._nc.isEmpty) {
return self._nc
}
let viewMargins = self.webview.layoutMarginsGuide
let minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height)
let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor)
centerY.constant = -1000.0
centerY.priority = (950)
let centerX = self.ratingView.centerXAnchor.constraintEqualToAnchor(self.webview.centerXAnchor)
centerX.priority = (950)
if let buttonConstraints = self.originalRatingViewConstraints?.filter({
($0.firstItem is UIButton || $0.secondItem is UIButton )
}) {
self._nc.appendContentsOf(buttonConstraints)
}
self._nc.append( centerY)
self._nc.append( centerX)
self._nc.append (self.ratingView.leadingAnchor.constraintEqualToAnchor(viewMargins.leadingAnchor, constant: 10.0))
self._nc.append (self.ratingView.trailingAnchor.constraintEqualToAnchor(viewMargins.trailingAnchor, constant: 10.0))
self._nc.append (self.ratingView.widthAnchor.constraintEqualToConstant((minimumScreenWidth - 20.0)))
self._nc.append (self.ratingView.heightAnchor.constraintEqualToConstant(200.0))
return self._nc
}()
If you forget one of these tips or the more simple ones such as where to add the layoutIfNeeded, most likely nothing will happen: In which case you may have a half baked solution like this:
NB - Take a moment to read the AutoLayout Section Below and the
original guide. There is a way to use these techniques to supplement
your Dynamic Animators.
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in
//
if self.starTopInflectionPoint.constant < 0 {
//-3000
//offscreen
self.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0
self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
} else {
self.starTopInflectionPoint.constant = -3000
self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
}
}) { (success) -> Void in
//do something else
}
}
Snippet from the AutoLayout Guide (note the second snippet is for using OS X). BTW - This is no longer in the current guide as far as I can see. The preferred techniques continue to evolve.
Animating Changes Made by Auto Layout
If you need full control over animating changes made by Auto Layout, you must make your constraint changes programmatically. The basic concept is the same for both iOS and OS X, but there are a few minor differences.
In an iOS app, your code would look something like the following:
[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];
In OS X, use the following code when using layer-backed animations:
[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setAllowsImplicitAnimation: YES];
// Make all constraint changes here
[containerView layoutSubtreeIfNeeded];
}];
When you aren’t using layer-backed animations, you must animate the constant using the constraint’s animator:
[[constraint animator] setConstant:42];
For those who learn better visually check out this early video from Apple.
Pay Close Attention
Often in documentation there are small notes or pieces of code that lead to bigger ideas. For example attaching auto layout constraints to dynamic animators is a big idea.
Good Luck and May the Force be with you.
Working Solution 100% Swift 5.3
i have read all the answers and want to share the code and hierarchy of lines which i have used in all my applications to animate them correctly, Some solutions here are not working, you should check them on slower devices e.g iPhone 5 at this moment.
self.btnHeightConstraint.constant = 110
UIView.animate(withDuration: 0.27) { [weak self] in
self?.view.layoutIfNeeded()
}
I was trying to animate Constraints and was not really easy to found a good explanation.
What other answers are saying is totally true: you need to call [self.view layoutIfNeeded]; inside animateWithDuration: animations:. However, the other important point is to have pointers for every NSLayoutConstraint you want to animate.
I created an example in GitHub.
Working and just tested solution for Swift 3 with Xcode 8.3.3:
self.view.layoutIfNeeded()
self.calendarViewHeight.constant = 56.0
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
Just keep in mind that self.calendarViewHeight is a constraint referred to a customView (CalendarView). I called the .layoutIfNeeded() on self.view and NOT on self.calendarView
Hope this help.
There is an article talk about this:
http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was
In which, he coded like this:
- (void)handleTapFrom:(UIGestureRecognizer *)gesture {
if (_isVisible) {
_isVisible = NO;
self.topConstraint.constant = -44.; // 1
[self.navbar setNeedsUpdateConstraints]; // 2
[UIView animateWithDuration:.3 animations:^{
[self.navbar layoutIfNeeded]; // 3
}];
} else {
_isVisible = YES;
self.topConstraint.constant = 0.;
[self.navbar setNeedsUpdateConstraints];
[UIView animateWithDuration:.3 animations:^{
[self.navbar layoutIfNeeded];
}];
}
}
Hope it helps.
In the context of constraint animation, I would like to mention a specific situation where I animated a constraint immediately within a keyboard_opened notification.
Constraint defined a top space from a textfield to top of the container. Upon keyboard opening, I just divide the constant by 2.
I was unable to achieve a conistent smooth constraint animation directly within the keyboard notification. About half the times view would just jump to its new position - without animating.
It occured to me there might be some additional layouting happening as result of keyboard opening.
Adding a simple dispatch_after block with a 10ms delay made the animation run every time - no jumping.

Test whether a UIView is in the middle of animation

Is there any way to tell if a UIView is in the middle of an animation? When I print out the view object while it is moving I notice that there is an "animations" entry:
search bar should end editing: <UISearchBar: 0x2e6240; frame = (0 0; 320 88); text = ''; autoresize = W+BM; animations = { position=<CABasicAnimation: 0x6a69c40>; bounds=<CABasicAnimation: 0x6a6d4d0>; }; layer = <CALayer: 0x2e6e00>>
When the animation has stopped and I print the view, the "animations" entry is now gone:
search bar should end editing: <UISearchBar: 0x2e6240; frame = (0 0; 320 88); text = ''; autoresize = W+BM; layer = <CALayer: 0x2e6e00>>
A UIView has a layer (CALayer). You can send animationKeys to it, which will give you an array of keys which identify the animations attached to the layer. I suppose that if there are any entries, the animation(s) are running. If you want to dig even deeper have a look at the CAMediaTiming protocol which CALayer adopts. It does some more information on the current animation.
Important: If you add an animation with a nil key ([layer addAnimation:animation forKey:nil]), animationKeys returns nil.
iOS 9+ method, works even when layer.animationKeys contains no keys:
let isInTheMiddleOfAnimation = UIView.inheritedAnimationDuration > 0
From the docs:
This method only returns a non-zero value if called within a UIView
animation block.
Animations are attached in fact to the underlying Core Animation CALayer class
So I think you can just check myView.layer.animationKeys
I'm not sure of the context of the question but I had was attempting to find out if a view was animating before starting a second animation to avoid skipping. However there is a UIView animation option UIViewAnimationOptionBeginFromCurrentState that will combine the animations if necessary to give a smooth appearance. Thereby eliminating my need to know if the view was animating.
There are a lot of out-of-date answers here. I just needed to prevent a UIView animation being started if there was one running on the same view, so I created a category on UIView:-
extension UIView {
var isAnimating: Bool {
return (self.layer.animationKeys()?.count ?? 0) > 0
}
}
This way I can just check any view's animation status like this:-
if !myView.isAnimating {
UIView.animate(withDuration: 0.4) {
...
}
} else {
// already animating
}
This seems less fragile than keeping track of it with a state variable.
There is a hitch with the animationKeys trick.
Sometimes there could be a few animationKeys lingering after an animation is completed.
This means that a non-animating layer could return a set of animationKeys even if it isn't actually animating.
You can make sure that animationKeys are automatically removed by setting an animation's removedOnCompletion property to YES.
e.g.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"aPath"];
animation.removedOnCompletion = YES;
If you do this for all the animations you apply to your layer, it will ensure that when the layer isn't animating there are no animationKeys present.
Some of these didn't work for me. The reason for that is that these animations are asynchronous.
What I did is defined a property
#property BOOL viewIsAnimating;
And in my animation
[UIView animateWithDuration:0.25
animations:^{
viewIsAnimating = YES;
} completion:^(BOOL finished) {
if (finished) {
viewIsAnimating = NO;
}
}];
Ref to the question: UIView center position during animation
I compare the view's frame and layer.presentation()?.frame to check it is animating. If leftView is on the way to finish, the leftView.layer.presentation()?.frame does not equal to its frame:
if self.leftView.layer.presentation()?.frame == self.leftView.frame {
// the animation finished
} else {
// the animation on the way
}
But this method may not work if the view move to the end position during the animation. More condition check may be necessary.
You could query the presentation layer as suggested here My presentation layer does not match my model layer even though I have no animations
You can use the layer property of a UIView. CALayer has a property called animation keys, you can check its count if it is greater than 0.
if (view.layer.animationKeys.count) {
// Animating
}else {
// No
}
In the Documentation:
-(nullable NSArray<NSString *> *)animationKeys;
Returns an array containing the keys of all animations currently *
attached to the receiver. The order of the array matches the order *
in which animations will be applied.

Resources