How to do a native "Pulse effect" animation on a UIButton - iOS - 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)
}

Related

CATransition type fade seems broken

When working on this question, I notice that maybe fade of CATransitionType in CATransition is not working or broken. The rest of CATransitionType: moveIn, push, reveal works correctly.
I'm working on iPhone 13, iOS 16.0.
Code of the transition:
extension CALayer {
func makeFadeTransition() {
let transition = CATransition()
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
transition.duration = 0.5
transition.type = .fade
self.add(transition, forKey: nil)
}
}
Usage:
self.customView.layer.makeFadeTransition()
I know that we can switch to using UIView.animate combined with alpha to change opacity instead.
UIView.animate(withDuration: 0.15, animations: {
self.customView.alpha = 0.0
})
or using CABasicAnimation:
extension CALayer {
func makeFadeTransition() {
let transition = CABasicAnimation(keyPath: "opacity")
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
transition.duration = 0.5
transition.fromValue = 1.0
transition.toValue = 0.0
self.add(transition, forKey: nil)
}
}
But again, is CATransitionType type fade broken, or am I wrong at something?
You have to do something to change the layer...
For example, to fade-out / fade-in, we could do this:
extension CALayer {
func makeFadeTransition() {
let transition = CATransition()
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
// let's use duration of 2.5 to make it more obvious
transition.duration = 2.5
transition.type = .fade
// change the layer... opacity, backgroundColor, frame, etc
// fade transition doesn't like opacity of 0.0, so we'll use 0.01
self.opacity = self.opacity == 1.0 ? 0.01 : 1.0
// or, for example,
// "shrink" the layer by 20-points
//self.frame = self.frame.insetBy(dx: 20.0, dy: 20.0)
self.add(transition, forKey: nil)
}
}

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:

Animate label count in with CABasicAnimation in swift?

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)"
}
}
}
}

How can I move an image in Swift?

I would like to be able to move an object (in my case, an image "puppy") up 1 pixel every time a button is pressed. I've stumbled upon old Objective-C solutions as well as Swift code that was similar, but none that fit my specific problem. I would love to know an easy way to move my image. This is my code so far from what I could gather(I'm hoping it's unnecessarily long and can be reduced to a line or two):
#IBAction func tapButton() {
UIView.animateWithDuration(0.75, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.puppy.alpha = 1
self.puppy.center.y = 0
}, completion: nil)
var toPoint: CGPoint = CGPointMake(0.0, 1.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = NSValue(CGPoint: fromPoint)
movement.toValue = NSValue(CGPoint: toPoint)
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}
This way you can change a position of your imageView
#IBAction func tapButton() {
UIView.animateWithDuration(0.75, delay: 0, options: .CurveLinear, animations: {
// this will change Y position of your imageView center
// by 1 every time you press button
self.puppy.center.y -= 1
}, completion: nil)
}
And remove all other code from your button action.
CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
self.viewBall.layer.position = self.viewBall.layer.presentationLayer().position
}
var animation = CABasicAnimation(keyPath: "position")
animation.duration = ballMoveTime
var currentPosition : CGPoint = viewBall.layer.presentationLayer().position
animation.fromValue = NSValue(currentPosition)
animation.toValue = NSValue(CGPoint: CGPointMake(currentPosition.x, (currentPosition.y + 1)))
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
viewBall.layer.addAnimation(animation, forKey: "transform")
CATransaction.commit()
Replace viewBall to your image object
And also remove completion block if you don't want.
You can do this.
func clickButton() {
UIView.animateWithDuration(animationDuration, animations: {
let buttonFrame = self.button.frame
buttonFrame.origin.y = buttonFrame.origin.y - 1.0
self.button.frame = buttonFrame
}
}

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.

Resources