Animate label count in with CABasicAnimation in swift? - ios

I have added a CABasicAnimation on CAShapeLayer.Now in this animation my CAShapeLayer create an arc on circle with animation.
Now what I want when this animation proceed count of label should increase with a sync to animation of CAShapeLayer. PLease how can I do it?
let animation = CABasicAnimation(keyPath: "strokeEnd")
//animation.delegate = self
animation.duration = 2
animation.fromValue = 0
animation.toValue = 0.7 // changed here
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.isRemovedOnCompletion = false
progressCircle.add(animation, forKey: "ani")
Here ProgressCircle is the CAShapeLayer.
I used below code for increement label count with animation but it does not match with the animation.
func incrementLabel(to endValue: Int) {
let duration: Double = 1.0 //seconds
DispatchQueue.global().async {
for i in 0 ..< (endValue + 1) {
let sleepTime = UInt32(duration/Double(endValue) * 1000000.0)
usleep(sleepTime)
DispatchQueue.main.async {
self.label.text = "\(i)"
}
}
}
}

Related

CABasicAnimation performance before removeFromSuperView()

I have a code-created button. Every time I click, I want it to perform CABasicAnimation before removeFromSuperView(). But if I add removeFromSuperView() after that, it will instantly removeFromSuperView() without any animation.
#objc func bubblePressed(_ bubble:Bubble){
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
bubble.layer.add(animation, forKey:nil)
bubble.removeFromSuperview()
}
Is there any way that I can achieve this?
#objc func bubblePressed(_ bubble:Bubble){
CATransaction.begin()
CATransaction.setCompletionBlock({
// remove from super view
bubble.removeFromSuperview()
})
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
bubble.layer.add(animation, forKey:nil)
CATransaction.commit()
}
Workaround 2
#objc func bubblePressed(_ bubble:Bubble){
let animation = CABasicAnimation(keyPath:"opacity")
animation.fromValue = 1
animation.toValue = 0
animation.duration = 2.0
animatio.delegate = self
bubble.layer.add(animation, forKey:nil)
}
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
bubble.removeFromSuperview()
// remove from super view in this function .. its Delegate method
}
}

How to read keyPath value after animation?

I'm animating show and hide of my FAB button, however I want to prevent showing FAB if it is already shown. How to read current layer scale?
func hideFab(){
let materialCurve = MDCAnimationTimingFunction.deceleration
let timingFunction = CAMediaTimingFunction.mdc_function(withType: materialCurve)
let animation = CABasicAnimation(keyPath:"transform.scale.xy")
animation.timingFunction = timingFunction
animation.fromValue = 1
animation.toValue = 0
animation.duration = 0.5
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
fab.layer.playAnimation({ (layer) -> CAAnimation in
animation
}, key: animation.keyPath!)
}
func showFab(){
let materialCurve = MDCAnimationTimingFunction.deceleration
let timingFunction = CAMediaTimingFunction.mdc_function(withType: materialCurve)
let animation = CABasicAnimation(keyPath:"transform.scale.xy")
animation.timingFunction = timingFunction
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.5
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
fab.layer.playAnimation({ (layer) -> CAAnimation in
animation
}, key: animation.keyPath!)
}
With this let currentScale = fab.layer.presentation()?.value(forKeyPath: "transform.scale.xy") ?? 0.0 you should be able to get the presentation scale value.
But I'd try to instead check if I showed or not the FAB with a tag:
let hiddenFABTag = 99
let shownFABTag = 100
func hideFAB(){
if fab.tag == shownFABTag {
...
...
fab.tag = hiddenFABTag
}
}
func showFAB(){
if fab.tag == hiddenFABTag {
...
...
fab.tag = shownFABTag
}
}
The first time you call it, either in viewDidLoad() or somewhere, you might want to give it a default tag value, if you know if it's going to be shown or hidden the first time by saying:
override func viewDidLoad() {
super.viewDidLoad()
fab.tag = hiddenFABTag
}

Animating CALayer border alpha

I have a UIView with a border (color: Green, width: 10).
I'm trying to animate the border's alpha (in a loop) from the value of 1.0 to the value of 0.2 - then back to 1.0 - then back to 0.2 etc...
But CALayer doesn't have a property of borderAlpha so I'm not sure how can I do that.
I've tried this code, but it didn't work:
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
self.layer.borderColor = UIColor(cgColor: self.layer.borderColor!).withAlphaComponent(0.2).cgColor
}, completion: nil)
Does anybody know how can I do that?
Thank you!
Try using this
class animateStack: UIViewController {
#IBOutlet weak var animateView: UIView!{
didSet{
animateView.layer.borderColor = UIColor.black.cgColor
animateView.layer.borderWidth = 10
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
animateBorderAlpha()
}
private func animateBorderAlpha(){
/// First Animation
let animation = CABasicAnimation(keyPath: "borderColor")
animation.beginTime = 0
animation.toValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation.fromValue = UIColor.black.cgColor
animation.duration = 2
/// Second Animation
let animation1 = CABasicAnimation(keyPath: "borderColor")
animation1.toValue = UIColor.black.cgColor
animation1.fromValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation1.beginTime = animation.beginTime + animation.duration
animation.duration = 4
/// Animation Group
let borderColorAnimation: CAAnimationGroup = CAAnimationGroup()
borderColorAnimation.animations = [animation, animation1]
borderColorAnimation.duration = animation.duration + animation1.duration
borderColorAnimation.repeatCount = Float.greatestFiniteMagnitude
self.animateView.layer.add(borderColorAnimation, forKey: "borderColor")
}
}
Update
class animateViewClass: NSObject {
class func animateBorderAlpha(_ view: UIView){
/// First Animation
let animation = CABasicAnimation(keyPath: "borderColor")
animation.beginTime = 0
animation.toValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation.fromValue = UIColor.black.cgColor
animation.duration = 2
/// Second Animation
let animation1 = CABasicAnimation(keyPath: "borderColor")
animation1.toValue = UIColor.black.cgColor
animation1.fromValue = UIColor.black.withAlphaComponent(0.1).cgColor
animation1.beginTime = animation.beginTime + animation.duration
animation.duration = 4
/// Animation Group
let borderColorAnimation: CAAnimationGroup = CAAnimationGroup()
borderColorAnimation.animations = [animation, animation1]
borderColorAnimation.duration = animation.duration + animation1.duration
borderColorAnimation.repeatCount = Float.greatestFiniteMagnitude
view.layer.add(borderColorAnimation, forKey: "borderColor")
}
}
Usage
animateViewClass.animateBorderAlpha(viewName)
/// Case of Subclass UIView
animateViewClass.animateBorderAlpha(self)
Updated and simplified. Use CALayer to create the border layer, then use CABasicAnimation to achieve fade-in-out effect:
class BorderView: UIView {
private var boarderLayer:CALayer?
private let animationKey = "opacityAnimation"
override public var frame: CGRect {
didSet{
self.updateBorder()
}
}
func updateBorder() {
if boarderLayer == nil {
boarderLayer = CALayer()
boarderLayer?.borderColor = UIColor.red.cgColor
boarderLayer?.borderWidth = 5.0
self.layer.addSublayer(boarderLayer!)
}
boarderLayer?.frame = self.bounds
if (boarderLayer?.animation(forKey: animationKey) == nil) {
self.addAnimiation(layer: boarderLayer!,increasing:true)
}
}
func addAnimiation(layer:CALayer,increasing:Bool) {
CATransaction.begin()
CATransaction.setCompletionBlock{ [weak self] in
self?.addAnimiation(layer: layer,increasing:!increasing)
}
let animation = CABasicAnimation(keyPath: "opacity")
if increasing {
layer.opacity = 0.2
animation.fromValue = 1.0
animation.toValue = 0.2
}
else{
layer.opacity = 1.0
animation.fromValue = 0.2
animation.toValue = 1.0
}
animation.duration = 1.0
layer.add(animation, forKey: animationKey)
CATransaction.commit()
}
}
And the result:

Pause and Resume UIViewAnimation when app goes to background

I am animating a view and I want to pause it and resume it.
Using an apple guide I created a CALayer Extension
extension CALayer {
func pause() {
var pauseTime = self.convertTime(CACurrentMediaTime(), fromLayer: nil)
self.speed = 0.0
self.timeOffset = pauseTime
}
func resume() {
var pausedTime = self.timeOffset
self.speed = 1.0
self.timeOffset = 0.0
self.beginTime = 0.0
var timeSincePause = self.convertTime(CACurrentMediaTime(), toLayer: nil) - pausedTime
self.beginTime = timeSincePause
}
}
This code is working perfectly except when that app goes to background. When I bring the App back to foreground animations is finished (even if the time is not pass) and it is not starting again when I click resume.
Ok. I tried animating CALayer but I have the same problem.
extension CALayer {
func animateY(newY:CGFloat,time:NSTimeInterval,completion:()->Void){
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
let animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = self.position.y
animation.toValue = newY
animation.duration = time
animation.delegate = self
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.removedOnCompletion = false // don't remove after finishing
self.position.y = newY
self.addAnimation(animation, forKey: "position.y")
CATransaction.flush()
}
}
I recommend using CABasicAnimation. Your resume/pause methods should be fine, since they are from this answer. You should try using Core Animation instead of UIViewAnimation and then the resume/pause will work.
Then you can register for the two notifications UIApplicationWillEnterForegroundNotification and UIApplicationDidEnterBackgroundNotification to have full control over the pause/resume actions.

How to do a native "Pulse effect" animation on a UIButton - iOS

I would like to have some kind of pulse animation (infinite loop "scale in - scale out") on a UIButton so it gets users' attention immediately.
I saw this link How to create a pulse effect using -webkit-animation - outward rings but I was wondering if there was any way to do this only using native framework?
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"opacity"];
theAnimation.duration=1.0;
theAnimation.repeatCount=HUGE_VALF;
theAnimation.autoreverses=YES;
theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
theAnimation.toValue=[NSNumber numberWithFloat:0.0];
[theLayer addAnimation:theAnimation forKey:#"animateOpacity"]; //myButton.layer instead of
Swift
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
pulseAnimation.duration = 1
pulseAnimation.fromValue = 0
pulseAnimation.toValue = 1
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .greatestFiniteMagnitude
view.layer.add(pulseAnimation, forKey: "animateOpacity")
See the article "Animating Layer Content"
Here is the swift code for it ;)
let pulseAnimation = CABasicAnimation(keyPath: "transform.scale")
pulseAnimation.duration = 1.0
pulseAnimation.fromValue = NSNumber(value: 0.0)
pulseAnimation.toValue = NSNumber(value: 1.0)
pulseAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .greatestFiniteMagnitude
self.view.layer.add(pulseAnimation, forKey: nil)
The swift code is missing a fromValue, I had to add it in order to get it working.
pulseAnimation.fromValue = NSNumber(value: 0.0)
Also forKey should be set, otherwise removeAnimation doesn't work.
self.view.layer.addAnimation(pulseAnimation, forKey: "layerAnimation")
func animationScaleEffect(view:UIView,animationTime:Float)
{
UIView.animateWithDuration(NSTimeInterval(animationTime), animations: {
view.transform = CGAffineTransformMakeScale(0.6, 0.6)
},completion:{completion in
UIView.animateWithDuration(NSTimeInterval(animationTime), animations: { () -> Void in
view.transform = CGAffineTransformMakeScale(1, 1)
})
})
}
#IBOutlet weak var perform: UIButton!
#IBAction func prefo(sender: AnyObject) {
self.animationScaleEffect(perform, animationTime: 0.7)
}

Resources