How do change the speed/duration of a state change? - framerjs

For normal animations, I can just use the following to change the time an animation takes to complete.
myLayer.on Events.Click, ->
myLayer.animate
properties: {x:200}
time: .01
However, this doesn't seem to work for state based animations like:
goop.on Events.Click, ->
goop.states.next("one", "default")
time: .2
How do I change the speed of animating state to state?

goop.states.animationOptions =
curve: 'linear'
time: .1

Related

UIViewControllerAnimatedTransitioning transtionsDuration and UIView.Animate

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!

Custom animation: easing out

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.

Observe UIView frame while animating [duplicate]

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.

UIViewPropertyAnimator different behaviour on iOS10 and iOS11 reversing an animation. Bug on `isReversed` and `fractionComplete` properties?

THE PROBLEM
Running the same code on iOS10 and iOS11 my UIViewPropertyAnimator has a different behaviour just after changing of his .isReversed property.
Everything is ok on iOS10. The animation problem happens on iOS11
CONDITIONS
It's true for any animations, not only for a particular one, and it is verifiable both by watching the animation and within the code.
It happens both on simulators and real devices.
DETAILS
Once created a UIViewPropertyAnimator with his animation, during its running I just call .pauseAnimation() and change the .isReversed property to true. After that I resume the animation calling:
continueAnimation(withTimingParameters parameters: UITimingCurveProvider?, durationFactor: CGFloat)
at this point on iOS10 the animation smoothly changes his verse, on iOS11 it stops immediately and reverses itself with a bit frames lag.
If in code I check the value of .fractionComplete (called on my UIViewPropertyAnimator object it gives me back the completion of the animation in his percent value, starting from 0.0 and ending at 1.0)
just after .continueAnimation(...
- On iOS 10 it remains for a few moments like if the animation is continuing and only after some fractions of time jumps to his complementary.
- On iOS 11 it jumps suddenly on his complementary
On the documentation there are non updates related to this, just a couple of new properties for the UIViewPropertyAnimator but not used because I'm targeting iOS10
Could be a bug or I'm missing something!?
Little update: just tested, same behaviour on iOS 11.0.1 and on iOS 11.1 beta1
As linked in the comment, this happens only with a non-linear curve!
I have been fighting this for quite a while as well, but then I noticed the scrubsLinearly property that was added to UIViewPropertyAnimator in iOS 11:
Defaults to true. Provides the ability for an animator to pause and scrub either linearly or using the animator’s current timing.
Note that the default of this property is true, which seems to cause a conflict with using a non-linear animation curve. That might also explain why the issue is not present when using a linear timing function.
Setting scrubsLinearly to false, the animator seems to work as expected:
let animator = UIViewPropertyAnimator(duration: 0.25, curve: .easeOut) {
...
}
animator.scrubsLinearly = false
On iOS 11, fractionComplete will be reversed (that is, 1 - originalFractionComplete) after you reverse animation by animator.isReversed = true.
Spring animation that have less than 0.1s duration will complete instantly.
So you may originally want the reversed animation runs 90% of the entire animation duration, but on iOS 11, the reversed animation actually runs 10% duration because isReversed changed, and that 10% duration is less than 0.1s, so the animation will be completed instantly and looks like no animation happened.
How to fix?
For iOS 10 backward compatibility, copy the fractionComplete value before you reverse animation and use it for continueAnimation.
e.g.
let fraction = animator.fractionComplete
animator.isReversed = true
animator.continueAnimation(...*fraction*...)
I have tried many solutions, but no one didn't work for me. I wrote my solution and everything is fine now. My solution:
take an image of the screen and show it
finish animation
start new animation for old state
pause the animation and set progress (1 - original progress)
remove screen image and continue animation
switch pan.state {
...
case .ended, .cancelled, .failed:
let velocity = pan.velocity(in: view)
let reversed: Bool
if abs(velocity.y) < 200 {
reversed = progress < 0.5
} else {
switch state {
case .shown:
reversed = velocity.y < 0
case .minimized:
reversed = velocity.y > 0
}
}
if reversed {
let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
view.addSubview(overlayView)
animator?.stopAnimation(false)
animator?.finishAnimation(at: .end)
startAnimation(state: state.opposite)
animator?.pauseAnimation()
animator?.fractionComplete = 1 - progress
overlayView.removeFromSuperview()
animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0.5)
} else {
animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0)
}
And the animation curve option must be linear.
animator = UIViewPropertyAnimator(duration: 0.3, curve: .linear) {
startAnimation()
}

Observing change in frame of a UIView during animation

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.

Resources