I have successfully created a custom animation in a drawing in a UIView by using a repeating Timer to increment the alpha of the drawing every few milliseconds.
Now, I want to achieve an easing out animation (deceleration) with my drawing. I would like to do this by firing a new timer with a longer interval every time the Timer is called, so that the alpha increments slower, resulting in deceleration.
I know that there is an easeOut animation from CAMediaTiming, but I would like to know if there is any built in function to get the decelerating numbers. For example, if I pass in a constant of 10, every time I call the function I can get decelerating numbers like 15, 18, 20, 21, 21.5, etc.
There is a built-in way to use various sorts of animation curves in standard UIView animations.
var yourView = UIView() // Or whatever your view might be
var yourView.alpha = 0 // Initial alpha value
// Call the animation method. Note options (which can be an array) which applies an ease-out curve to the animation
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseOut, animations: {
yourView.alpha = 1.0 // Final value of alpha
}, completion: nil)
Other animation curves are available, such as .curveEaseIn and .curveEaseInOut as well as other options. You can read more about animation options here.
You can use the completion handler closure to chain animations too.
If you are insisting on implementing your own means of animation using a timer (which I would not recommend), post some code so that I can consider a way to apply a curve to the changing values.
Related
I have a subclass of UIViewControllerAnimatingTransitioning which defines an animations in a method animateTransitioning(using:) with UIView.animate(withDuration: 1) and second one UIView.animate(withDuration: 2, delay: 0.5, options: .curveEaseIn). The UIViewControllerAnimatingTransitioning requires a second method which is transitionDuration(using:) which should return a duration.
How duration defined in transitionDuration(using:) impact the duration defined in UIView.animation(withDuration)?
According to the documentation provided by Apple for the transitionDuration(using:) method here, the duration you return is used to synchronize other potential animations and "actions".
UIKit uses the value to synchronize the actions of other objects that might be involved in the transition. For example, a navigation controller uses the value to synchronize changes to the navigation bar.
In your scenario where there are two different UIView.animate blocks, you should return the time it takes for the longest animation to complete. Below is the explanation on how to calculate this. Assume both animations start at t = 0, where t is time in seconds.
totalAnimationDuration = delay + duration
First animation starts at, t = 0 and completes after 1 second.
Second animation starts at, t = 0.5 and completes after 2 seconds.
let animOneTotalDuration = 0 + 1 // 1 second
let animTwoTotalDuration = 0.5 + 2 // 2.5 seconds
Since the animation that has the maximum total time is the second animation, you should return 2.5 at transitionDuration(using:).
It should also be noted that setting the animation duration correctly does not automatically complete the transition after the duration. You must still invoke the completeTransition(_:) of the UIViewControllerContextTransitioning object you receive at animateTransition(using:).
Hope I was able to give you some insight!
I want to observe changes to the x coordinate of my UIView's origin while it is being animated using animateWithDuration:delay:options:animations:completion:. I want to track changes in the x coordinate during this animation at a granular level because I want to make a change in interaction to another view that the view being animated may make contact with. I want to make that change at the exact point of contact. I want to understand the best way to do something like this at a higher level:
-- Should I use animateWithDuration:... in the completion call back at the point of contact? In other words, The first animation runs until it hits that x coordinate, and the rest of the animation takes place in the completion callback?
-- Should I use NSNotification observers and observe changes to the frame property? How accurate / granular is this? Can I track every change to x? Should I do this in a separate thread?
Any other suggestions would be welcome. I'm looking for a abest practice.
Use CADisplayLink since it is specifically built for this purpose. In the documentation, it says:
Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated.
For me I had a bar that fills up, and as it passed a certain mark, I had to change the colors of the view above that mark.
This is what I did:
let displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink.frameInterval = 3
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
UIView.animateWithDuration(1.2, delay: 0.0, options: [.CurveEaseInOut], animations: {
self.viewGaugeGraph.frame.size.width = self.graphWidth
self.imageViewGraphCoin.center.x = self.graphWidth
}, completion: { (_) in
displayLink.invalidate()
})
func animationDidUpdate(displayLink: CADisplayLink) {
let presentationLayer = self.viewGaugeGraph.layer.presentationLayer() as! CALayer
let newWidth = presentationLayer.bounds.width
switch newWidth {
case 0 ..< width * 0.3:
break
case width * 0.3 ..< width * 0.6:
// Color first mark
break
case width * 0.6 ..< width * 0.9:
// Color second mark
break
case width * 0.9 ... width:
// Color third mark
break
default:
fatalError("Invalid value observed. \(newWidth) cannot be bigger than \(width).")
}
}
In the example, I set the frameInterval property to 3 since I didn't have to rigorously update. Default is 1 and it means it will fire for every frame, but it will take a toll on performance.
create a NSTimer with some delay and run particular selector after each time lapse. In that method check the frame of animating view and compare it with your colliding view.
And make sure you use presentationLayer frame because if you access view.frame while animating, it gives the destination frame which is constant through out the animation.
CGRect animationViewFrame= [[animationView.layer presentationLayer] frame];
If you don't want to create timer, write a selector which calls itself after some delay.Have delay around .01 seconds.
CLARIFICATION->
Lets say you have a view which you are animating its position from (0,0) to (100,100) with duration of 5secs. Assume you implemented KVO to the frame of this view
When you call the animateWithDuration block, then the position of the view changes directly to (100,100) which is final value even though the view moves with intermediate position values.
So, your KVO will be fired one time at the instant of start of animation.
Because, layers have layer Tree and Presentation Tree. While layer tree just stores destination values while presentation Layer stores intermediate values.
When you access view.frame it will always gives the value of frame in layer tree not the intermediate frames it takes.
So, you had to use presentation Layer frame to get intermediate frames.
Hope this helps.
UIDynamics and collision behaviours would be worth investigating here. You can set a delegate which is called when a collision occurs.
See the collision behaviour documentation for more details.
I'm following this tutorial. The code snippet can be found here.In some animations, the animation being messaged directly onto the UIView. Like the code snippet below.
Question1: What property of what class is this UIView? Or is it that we are messaging onto the UIView of the everything on the screen do what's in the code block? I'm just trying to understand which property we are messaging here...
protocol Flashable {}
extension Flashable where Self: UIView {
func flash() {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn, animations: {
self.alpha = 1.0
}) { (animationComplete) in
if animationComplete == true {
UIView.animate(withDuration: 0.3, delay: 2.0, options: .curveEaseOut, animations: {
self.alpha = 0.0
}, completion: nil)
}
}
}
}
^^^I don't get UIView.animate part. ^^^
And for some it's not directly messaged to the UIView.
extension Jitterable where Self: UIView {
func jitter() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint.init(x: self.center.x - 5.0, y: self.center.y))
animation.toValue = NSValue(cgPoint: CGPoint.init(x: self.center.x + 5.0, y: self.center.y))
layer.add(animation, forKey: "position")
}
}
^^^The above code is more understandable^^^
However when you want to call them both you do:
errorLabel.flash()
errorLabel.jitter()
Question2: what's the difference between the way both animations are written?
An animation block is global over all views. A given animation block is free (and encouraged) to animate as many views as it would like at the same time. So this is naturally a "global" function, not tied to any specific view.
In object oriented programming, it is common to implement "global" functions as a static/class method on some relevant class. This improves name spacing (and occasionally has other benefits, such as access to class data). That's all that's happening here. UIView is a convenient and expressive place to nest the global animate(withDuration:...) methods. But these methods are really just global functions. There's no strict rule that they even have to animate views.
In your CABasicAnimation code, there's an implicit global CATransaction created as well that wraps up all the current animations. So the "more understandable" version is also a bit more magical and is hiding some of the more complicated parts from you (in a way that can bite you if you don't understand it). The UIView version is actually a bit more explicit in what's going on, especially if you animated multiple things together. That's one reason the UIView wrapper was added, and why it is typically the better approach for animating views.
The animate(withDuration:animations:completion:) function is a type method of UIView.
This means you can just call the function on the Type UIView as you can see in your first code snippet. You can find more information on type methods in the Swift Language Guide
The second argument to this function is the animation you want to be performed. This can be an animation on a single view (in this case on self) or any other amount of views and animations.
The second version of animation is a CABasicAnimation. You set up the animation and then attach it to the specific layer you want.
As the documentation says, a CABasicAnimation is
An object that provides basic, single-keyframe animation capabilities for a layer property.
I have a superview A and a subview B. In A's drawRect(_:) I do some line drawing that depends on the position and size of subview B. In class A (but not in its drawRect(_:)) I also have an animation block that moves and scales B.
The problem is that the drawing in drawRect(_:) always happens before the animation takes place, either using the final position and size of the subview or using its initial position and size, but never using the intermediate values.
Case 1: drawing uses the final state
The image below is a screen shot of the simulator while the animation that scales the subview is in progress. Note how the lines are already drawn in their final state.
The code for the line drawing is in A's drawRect(_:). This is the code for the animation (it's in class A so self refers to the superview):
UIView.animateWithDuration(3.0, delay: 0.5,
options: UIViewAnimationOptions.CurveLinear,
animations: {
[unowned self] in
self.subviewWidthConstraint.constant = 0.75 * CGRectGetWidth(self.bounds)
self.layoutIfNeeded()
self.setNeedsDisplay() // this has no effect since self's bounds don't change
}) { (completed) -> Void in }
Case 2: drawing uses the initial state
Looking around for answers I found a suggestion to use self.layer.displayIfNeeded() in place of self.setNeedsDisplay() in the animation block, so I tried this as well
func animateFrame()
{
UIView.animateWithDuration(3.0, delay: 0.5,
options: UIViewAnimationOptions.CurveLinear,
animations: {
[unowned self] in
self.subviewWidthConstraint.constant = 0.75 * CGRectGetWidth(self.bounds)
self.layoutIfNeeded()
self.layer.displayIfNeeded() // the only difference is here
}) { (completed) -> Void in }
}
but it results in the following. Again, this is a screen shot while the animation that scales up the subview is in progress. Now, the lines are again drawn before the animation starts but using the subview's initial position and size.
I think I understand case 1. The entire animation block is queued for execution but its end result is already computed by the time the animation starts so it's no surprise that drawInRect(_:) is using the final state.
I don't understand case 2 but that's because I have little experience with dealing directly with the view layer.
Either way, the effect I'm looking to achieve is that the lines are drawn to the vertices of the subview while it's being moved and/or scaled. Any suggestions or pointers to documentation are much appreciated. Thanks!
I want to observe changes to the x coordinate of my UIView's origin while it is being animated using animateWithDuration:delay:options:animations:completion:. I want to track changes in the x coordinate during this animation at a granular level because I want to make a change in interaction to another view that the view being animated may make contact with. I want to make that change at the exact point of contact. I want to understand the best way to do something like this at a higher level:
-- Should I use animateWithDuration:... in the completion call back at the point of contact? In other words, The first animation runs until it hits that x coordinate, and the rest of the animation takes place in the completion callback?
-- Should I use NSNotification observers and observe changes to the frame property? How accurate / granular is this? Can I track every change to x? Should I do this in a separate thread?
Any other suggestions would be welcome. I'm looking for a abest practice.
Use CADisplayLink since it is specifically built for this purpose. In the documentation, it says:
Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated.
For me I had a bar that fills up, and as it passed a certain mark, I had to change the colors of the view above that mark.
This is what I did:
let displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink.frameInterval = 3
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
UIView.animateWithDuration(1.2, delay: 0.0, options: [.CurveEaseInOut], animations: {
self.viewGaugeGraph.frame.size.width = self.graphWidth
self.imageViewGraphCoin.center.x = self.graphWidth
}, completion: { (_) in
displayLink.invalidate()
})
func animationDidUpdate(displayLink: CADisplayLink) {
let presentationLayer = self.viewGaugeGraph.layer.presentationLayer() as! CALayer
let newWidth = presentationLayer.bounds.width
switch newWidth {
case 0 ..< width * 0.3:
break
case width * 0.3 ..< width * 0.6:
// Color first mark
break
case width * 0.6 ..< width * 0.9:
// Color second mark
break
case width * 0.9 ... width:
// Color third mark
break
default:
fatalError("Invalid value observed. \(newWidth) cannot be bigger than \(width).")
}
}
In the example, I set the frameInterval property to 3 since I didn't have to rigorously update. Default is 1 and it means it will fire for every frame, but it will take a toll on performance.
create a NSTimer with some delay and run particular selector after each time lapse. In that method check the frame of animating view and compare it with your colliding view.
And make sure you use presentationLayer frame because if you access view.frame while animating, it gives the destination frame which is constant through out the animation.
CGRect animationViewFrame= [[animationView.layer presentationLayer] frame];
If you don't want to create timer, write a selector which calls itself after some delay.Have delay around .01 seconds.
CLARIFICATION->
Lets say you have a view which you are animating its position from (0,0) to (100,100) with duration of 5secs. Assume you implemented KVO to the frame of this view
When you call the animateWithDuration block, then the position of the view changes directly to (100,100) which is final value even though the view moves with intermediate position values.
So, your KVO will be fired one time at the instant of start of animation.
Because, layers have layer Tree and Presentation Tree. While layer tree just stores destination values while presentation Layer stores intermediate values.
When you access view.frame it will always gives the value of frame in layer tree not the intermediate frames it takes.
So, you had to use presentation Layer frame to get intermediate frames.
Hope this helps.
UIDynamics and collision behaviours would be worth investigating here. You can set a delegate which is called when a collision occurs.
See the collision behaviour documentation for more details.