Mac OS - swift - CABasicAnimation current value - swift

I have a CABasicAnimation moving a NSHostingView (it's a scrolling marquee text). When the user get his mouse cursor over the view, the animation must stops and it can interact with the view.
I tried the following approaches:
Method 1:
As recommended by Apple documentation (https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW14), I tried to implement the pause/resume method using the convertTime / speed = 0.
It works, the animation is paused and resumed, but while the animation is paused, the coordinates of the user clic inside the view are the one of the model layer and not the one of the presentation layer. So the click respond to where the user would have click if their was no animation.
Method 2:
I tried the various animation flags on my CABasicAnimation as follow :
let animation = CABasicAnimation(keyPath: "position");
animation.duration = 10
animation.fromValue = [0, self.frame.origin.y]
animation.toValue = [-500, self.frame.origin.y]
animation.isCumulative = true
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
Not matter what I use, when the animation is removed, the view get back to it's original position (it jumps back to it's original position).
Method 3:
I tried to save the current animation value before removing by reading the presentation layer position as follow:
print("before")
let currentPosition = layer.presentation()?.position
print("after")
But this method never returns! (ie. "after" is never printed). Could it be a bug or something?
I'm out of idea now. If anyone has a suggestion, it would help a lot. Thanks!

Accessing layer?.presentation()?.position doesn't work because animation.fromValue and animation.toValue should be CGPoint values:
animation.fromValue = CGPoint(x: 0, y: self.frame.origin.y)
animation.toValue = CGPoint(x: -500, y: self.frame.origin.y)
Then Method 3 works just fine.

Related

How to repeat same animation while working with CABasicAnimation [duplicate]

This question already has answers here:
CABasicAnimation unlimited repeat without HUGE_VALF?
(2 answers)
Closed 2 years ago.
I am creating my own custom activity indicator. I created a circular view with CAShapeLayer and I managed to stroke the circular layer but I want to do it indefinitely until the user wants to stop. The following is my stroke layer animation code.
private func getStrokeEndAnimation()->CABasicAnimation{
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 2.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
return animation
}
There is an instance property on BasicAnimation named repeatCount but if I specify that my activity indicator will animate the given number of times like if I do
animation.repeatCount = 3
it will animate only 3 times. How can I make sure that the animation keeps going indefinitely until, I stop it.
animation.repeatCount = .greatestFiniteMagnitude will for all practical purposes repeat it forever.
You can use infinity in repeat count according to Apple docs
Infinity compares greater than all finite numbers and equal to other
infinite values.
animation.repeatCount = .infinity

How to smoothly finish infinity animation

I have infinity CABasicAnimation which actually simulate pulsating by increasing and decreasing scale:
scaleAnimation.fromValue = 0.5
scaleAnimation.toValue = 1.0
scaleAnimation.duration = 0.8
scaleAnimation.autoreverses = true
scaleAnimation.repeatCount = .greatestFiniteMagnitude
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
I want to smoothly stop this animation in toValue. In other words, I want to allow current animation cycle finish, but stop repeating. Is there a nice and clean way to do this? I had a few ideas about freezing current animation, removing it and creating a new one with time offset, but maybe there is a better way?
There is a standard way to do this cleanly — though it's actually quite tricky if you don't know about it:
The first thing you do is set the layer's scale to the scale of its presentationLayer.
Then call removeAllAnimations on the layer.
Now do a fast animation where you set the layer's scale to 1.
Here's a possible implementation (for extra credit, I suppose we could adjust the duration of the fast animation to match what the current scale is, but I didn't bother to do that here):
#IBAction func doStop(_ sender: Any) {
let lay = v.layer
lay.transform = lay.presentation()!.transform
lay.removeAllAnimations()
CATransaction.flush()
lay.transform = CATransform3DIdentity
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.duration = 0.4
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
lay.add(scaleAnimation, forKey: nil)
}
Result:

How to trigger an animation by PERCENTAGE in swift?

Alright, I am trying to trigger an animation incrementally as a scroll view is scrolled. To do this, I have taken this link here and converted it to swift - http://www.oliverfoggin.com/controlling-animations-with-a-uiscrollview/
Giving me the percentage offset x for my scrollview. This is all great.
Problem is I'm fairly new to swift and don't know how to tie this back into my existing animation which is a transform/move instead of changing color.
Here's my animation here-
self.borderlines.transform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
self.activeBorder.layer.opacity = 1
CATransaction.begin()
self.activeBorder.layer.transform = CATransform3DMakeScale(CGFloat(0.03), CGFloat(1.0), 1)
let anim2 = CABasicAnimation(keyPath: "transform")
let fromTransform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
let toTransform = CATransform3DMakeScale(CGFloat(1.0), CGFloat(1.0), 1)
anim2.fromValue = NSValue(CATransform3D: fromTransform)
anim2.toValue = NSValue(CATransform3D: toTransform)
anim2.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
anim2.fillMode = kCAFillModeForwards
anim2.removedOnCompletion = false
self.activeBorder.layer.addAnimation(anim2, forKey: "_activeBorder")
CATransaction.commit()
And then I also have another move animation. As is these are called on touch, so 1 static event.
The guy in the tutorial from what I can see recalculated/triggers his animation EVERY scroll then alters bg color by percentage. I don't know how to apply this to another type of animation- he mentions key frames and I have no idea what those are.
How can I achieve this effect? What do I need to change here?
Look into "freezing" your animation by setting its layer's speed to zero and manipulating its timeOffset as your response to scrolling.

CABasicAnimation with keypath "bounds" not working

I have the following code to animation bounds property of CALayer using CABasicAnimation. But the code doesn't seem to work.
let fromValue = textLabel.layer.bounds
let toValue = CGRectMake(textLabel.layer.bounds.origin.x, textLabel.layer.bounds.origin.y, textLabel.layer.bounds.width, textLabel.layer.bounds.height + 50)
let positionAnimation = CABasicAnimation(keyPath: "bounds")
positionAnimation.fromValue = NSValue(CGRect: fromValue)
positionAnimation.toValue = NSValue(CGRect: toValue)
positionAnimation.duration = 1
positionAnimation.fillMode = kCAFillModeBoth
positionAnimation.removedOnCompletion = false
textLabel.layer.addAnimation(positionAnimation, forKey: "bounds")
Your code does actually work. If you run your code and then switch on View Debugging in Xcode, you will see that the height of the label has increased. The "problem" is that a UILabel in iOS 8 draws itself (its text and its background, if it has one) the same way even after its layer height has been artificially increased in this way. (I believe that this is because the label draws itself with a special clipping region that is based on its text contents.)
To prove this to yourself, try it on a plain vanilla UIView (with a colored background) instead of a label. I've taken the liberty of cleaning up your code (you should never misuse fillMode and removedOnCompletion the way you are doing - it just shows a lack of understand of what animation is):
let fromValue = view2.layer.bounds.height
let toValue = view2.layer.bounds.height + 50
CATransaction.setDisableActions(true)
view2.layer.bounds.size.height = toValue
let positionAnimation = CABasicAnimation(keyPath: "bounds.size.height")
positionAnimation.fromValue = fromValue
positionAnimation.toValue = toValue
positionAnimation.duration = 1
view2.layer.addAnimation(positionAnimation, forKey: "bounds")
You will see that that works perfectly. Now change view2 back to textLabel throughout. It still works, it's just that there is nothing to see.
Another way to prove this to yourself is to drop the whole animation and just change the label's layer height:
self.textLabel.layer.bounds.size.height += 50
You will not see anything happen. So there is no bug in your animation; it's all about the way labels are drawn.
You can make the change visible by changing the view instead of the layer:
self.textLabel.bounds.size.height += 50
One additional "problem", though, is that the animation is not animating. Again this is because labels are drawn in a special way.
So whatever it is you're trying to accomplish it, you'll have to do it in a different way. You might have a clear label in front of a colored view and animate the change in height of the view; we've already proven that that works.

Animating a label to increase and decrease in size

I have been trying to implement an animation that brings the users attention to a change in value in a label. I want to do this by quickly increasing and reducing the size of the label (can't think of a better way to describe it) and I've made some progress towards this. The problem is that while the animation increases in size as I want it to; the way it deceases in size isn't smooth. Also, once the animation is complete, the size of the font does not return to the original.
Here is what I have:
func bloat() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDelegate(self)
UIView.setAnimationDelay(0.6)
UIView.setAnimationDuration(0.3)
UIView.setAnimationRepeatCount(4)
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
currentBudgetDisplay.transform = CGAffineTransformMakeScale(0.9, 0.9)
UIView.commitAnimations()
}
What changes can I make to get it to work as I intend it to?
The requirement is simple using core animation, try this
func bloat() {
var animation = CABasicAnimation(keyPath: "transform.scale")
animation.toValue = NSNumber(float: 0.9)
animation.duration = 0.3
animation.repeatCount = 4.0
animation.autoreverses = true
currentBudgetDisplay.layer.addAnimation(animation, forKey: nil)
}
In iOS 6 and 7, transform UIView animations applied to UIViews under auto layout do not work (because the auto layout stops them). This is fixed in iOS 8, however.
If you insist on supporting iOS 7, there are many workarounds, including using CABasicAnimation (layer animation) instead of UIView animation. See also my essay here: How do I adjust the anchor point of a CALayer, when Auto Layout is being used?