Animation stops working when view appears again - ios

extension UIView {
func startBlinking() {
UIView.animate(withDuration: 0.8, delay: 0.0, options: [.allowUserInteraction, .curveEaseInOut, .autoreverse, .repeat], animations: { self.alpha = 0 }, completion: nil)
}
}
How do I use it?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
monthLabel.startBlinking()
}
When method is called first time, then it blinks... but the second time when view did appear the label disappears and... that's it.
Why doesn't it work again?
My logs:
did load
did appear
start blinking
did appear
start blinking

Try:
import UIKit
protocol Then {}
extension Then {
func then(_ block: (Self) -> Void) -> Self {
block(self)
return self
}
}
extension UIView: Then {}
class ViewController: UIViewController {
private var animate: ((Bool) -> Void)?
private var canAnimate: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel().then {
$0.text = "Hello World!"
$0.textColor = .black
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
animate = { [weak self] (forward) in
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0.0, options: [.curveLinear, .autoreverse, .repeat], animations: {
label.alpha = forward ? 0.0 : 1.0
}, completion: { [weak self] _ in
if self?.canAnimate ?? true {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
self?.animate?(!forward)
})
}
}).startAnimation()
}
animate?(true)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let controller = UIViewController()
self.navigationController?.pushViewController(controller, animated: true)
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
canAnimate = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
canAnimate = true
animate?(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

You need to restart the alpha
func startBlinking() {
self.layer.removeAllAnimations()
self.alpha = 1.0
UIView.animate(withDuration: 0.8, delay: 0.0, options: [.allowUserInteraction, .curveEaseInOut], animations: { self.alpha = 0.0 }, completion: nil)
}

using CABasicAnimation solves my issue
let animation = CABasicAnimation(keyPath: "opacity")
animation.isRemovedOnCompletion = false
animation.fromValue = 1
animation.toValue = 0.1
animation.duration = 0.8
animation.autoreverses = true
animation.repeatCount = Float.infinity
animation.beginTime = 0.0
yourView.layer.add(animation, forKey: nil)

Related

Animation not working when home button is pressed and the app is relaunch again

My animation stopped running when I press the home button and then relaunch the app. The settings button just stop spinning and the blink label just faded away. Here is my code for both animation:
Blink animation:
extension UILabel {
func startBlink() {
UIView.animate(withDuration: 0.8,
delay:0.0,
options:[.autoreverse, .repeat],
animations: {
self.alpha = 0
}, completion: nil)
}
}
Rotating animation:
extension UIButton {
func startRotating() {
UIView.animate(withDuration: 4.0, delay: 0.0, options:[.autoreverse, .repeat,UIViewAnimationOptions.allowUserInteraction], animations: {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: nil)
}
}
Where I run it:
override func viewDidLoad() {
super.viewDidLoad()
settingsButton.layer.cornerRadius = 0.5 * settingsButton.bounds.size.width
settingsButton.clipsToBounds = true
settingsButton.imageView?.contentMode = .scaleAspectFit
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
func appMovedToForeground() {
tapToPlayLabel.startBlink()
settingsButton.startRotating()
print("DID")
}
To restart your animation you have to do below thing, please check below code.
Check extension
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tapToPlayLabel: UILabel!
#IBOutlet weak var settingsButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
settingsButton.layer.cornerRadius = settingsButton.frame.size.width/2
settingsButton.clipsToBounds = true
//settingsButton.imageView?.contentMode = .scaleAspectFit
settingsButton.startRotating()
tapToPlayLabel.startBlink()
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
func appMovedToForeground() {
self.tapToPlayLabel.startBlink()
self.settingsButton.startRotating()
}
}
extension UILabel {
func startBlink() {
self.alpha = 1
UIView.animate(withDuration: 0.8,
delay:0.0,
options:[.autoreverse, .repeat],
animations: {
self.alpha = 0
}, completion: nil)
}
}
extension UIButton {
func startRotating() {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
UIView.animate(withDuration: 4.0, delay: 0.0, options:[.autoreverse, .repeat,UIViewAnimationOptions.allowUserInteraction], animations: {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: nil)
}
}
Output
I think you need to run your animations with a little delay as there is already the delegate of app in execution while app is moving to foreground.
Or you can add CALayerAnimation on UILabel
extension UILabel {
func startBlink() {
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform")
scaleAnimation.delegate = self as? CAAnimationDelegate
let transform: CATransform3D = CATransform3DMakeScale(1.5, 1.5, 1)
scaleAnimation.values = [NSValue(caTransform3D: CATransform3DIdentity), NSValue(caTransform3D: transform), NSValue(caTransform3D: CATransform3DIdentity)]
scaleAnimation.duration = 0.5
scaleAnimation.repeatCount = 100000000
self.layer.add(scaleAnimation as? CAAnimation ?? CAAnimation(), forKey: "scaleText")
}
func startRotating() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fromValue = 0
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 2
rotation.isCumulative = true
rotation.repeatCount = .greatestFiniteMagnitude
self.layer.add(rotation, forKey: "rotationAnimation")
}
}

Pausing and resuming UIView animation with alpha doesn't work when user presses Home button

Question:
How can I pause a nested UIView animation that animates alpha/opacity when the user presses the Home button and returns back to the app?
What have I missed in the code below? Thank you.
Background:
I have a simple nested UIView animation that adjusts the alpha of a view slowly at different times from alpha = 0 to alpha = 1 over 1 minute.
When a user presses the Home button, the UIView animation does not pause, so when the app resumes, the UIView animation fasts forward to the very end of the animation, the alpha changes instantly from 0 to 1 on resuming the app.
So, I’m trying to pause the UIView animation when the user presses the device Home button and temporarily suspends the app.
I have a function that pauses the animation (pauseLayer) and then recommences the animation (resumeLayer). These two functions work great when calling them from a UIButton. It pauses the alpha and resumes the animation as expected. (However, if the Home button is pressed while the animation is paused, when it resumes the alpha changes instantly from 0 to 1.)
When I try calling the pauseLayer when the user presses the Home button (receiving the WillResignActive notification), and then returns back to the app (receiving the WillEnterForeground notification), the animation doesn’t pause and resume, instead the alpha changes instantly from 0 to 1 on resuming the app.
It would seem it should work, but it doesn’t.
Code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView1: UIView!
#IBOutlet weak var myView2: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setNotifications()
myAnimation()
}
#IBAction func myPauseButton(sender: UIButton) {
let layer = self.view.layer
pauseLayer(layer)
}
#IBAction func myResumeButton(sender: UIButton) {
let layer = self.view.layer
resumeLayer(layer)
}
func setNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.WillEnterForeground(_:)), name: UIApplicationWillEnterForegroundNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.WillResignActive(_:)), name: UIApplicationWillResignActiveNotification, object: nil)
}
func WillEnterForeground(notification : NSNotification) {
let layer = self.view.layer
resumeLayer(layer)
}
func WillResignActive(notification : NSNotification) {
let layer = self.view.layer
pauseLayer(layer)
}
func pauseLayer(layer: CALayer) {
let pausedTime: CFTimeInterval = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
layer.speed = 0.0
layer.timeOffset = pausedTime
}
func resumeLayer(layer: CALayer) {
let pausedTime: CFTimeInterval = layer.timeOffset
layer.speed = 1.0
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause: CFTimeInterval = layer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
layer.beginTime = timeSincePause
}
func myAnimation() {
self.myView1.alpha = 0
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView1.alpha = 0.1
}, completion: {(finished: Bool) in
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView1.alpha = 0.2
}, completion: {(finished: Bool) in
UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView1.alpha = 1
}, completion: nil)
})
})
self.myView2.alpha = 0
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView2.alpha = 0.1
}, completion: {(finished: Bool) in
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView2.alpha = 0.2
}, completion: {(finished: Bool) in
UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut], animations: {
self.myView2.alpha = 1
}, completion: nil)
})
})
}
}
EDIT:
If I add if (finished) { to each section, then press the Home button, then return to the app, the animation only progresses to the next section and stops, no further. This is better, but the issue then is that the resumeLayer doesn’t seem to work, so the animation remains stopped and doesn’t continue.
self.myView2.alpha = 0
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
self.myView2.alpha = 0.1
}, completion: {(finished: Bool) in
if (finished) {
UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
self.myView2.alpha = 0.2
}, completion: {(finished: Bool) in
if (finished) {
UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
self.myView2.alpha = 1
}, completion: nil)
}
})
}
})
You do not need to use UIView Animation to accomplish a simple fade.
You could use an Timer object and a little Math to manually animate it.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = colorScheme.red
NotificationCenter.default.addObserver(self, selector: #selector(self.didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
}
func didBecomeActive() {
if case min_t..<max_t = t {
beginAnimation(at: t)
}
}
func didResignActive() {
_ = pauseAnimation()
}
var t = TimeInterval(0.0)
let min_t = TimeInterval(0)
let max_t = TimeInterval(100)
let delta = TimeInterval(0.1)
var timerObj: Timer?
func timer(_ aTimer: Timer) {
if t < max_t {
// Using a parabola
view.alpha = CGFloat(0.0001 * t * t)
} else {
_ = pauseAnimation()
}
t = t + 1
}
func beginAnimation(at t: TimeInterval = 0) {
self.t = t
timerObj = Timer.scheduledTimer(timeInterval: delta, target: self, selector: #selector(self.timer), userInfo: nil, repeats: true)
}
func pauseAnimation() -> TimeInterval {
timerObj?.invalidate()
return t
}
override func viewDidAppear(_ animated: Bool) {
beginAnimation()
}
}
However, there is a possibility that your application is actually more complicated and your example is a simplification. Well, how about this?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
NotificationCenter.default.addObserver(self, selector: #selector(self.WillEnterForeground(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.WillResignActive(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
}
func WillEnterForeground(notification : NSNotification) {
if let timegap = endDate?.timeIntervalSince(startDate)
, timegap < duration {
beginAnimation(with: timegap/duration)
}
}
func WillResignActive(notification : NSNotification) {
let layer = self.view.layer
layer.removeAllAnimations()
}
var duration = TimeInterval(10)
var percentComplete = 0.0
var startDate: Date = Date()
var endDate: Date?
func beginAnimation(with percent: Double = 0.0) {
view.alpha = CGFloat(percent)
startDate = Date()
UIView.animateKeyframes(withDuration: duration * (1 - percent)
, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear,
animations: {
if percent < 0.2 {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
self.view.alpha = 0.1
})
}
if percent < 0.5 {
UIView.addKeyframe(withRelativeStartTime: 0.2, relativeDuration: 0.3, animations: {
self.view.alpha = 0.2
})
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.7, animations: {
self.view.alpha = 1.0
})
}) { (b: Bool) in
if b == false {
self.endDate = Date()
if let timegap = self.endDate?.timeIntervalSince(self.startDate)
, timegap < self.duration {
self.view.alpha = CGFloat(timegap / self.duration)
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
beginAnimation()
}
}
The main idea is to get the time before the animation starts and the time when it ends. The completion will tell us true if the animation was successful, but if the user presses the home button the completion block will tell us false. If it is false we can capture the date and find out the time difference and calculate the percent of completion. then we can use the percentage to determine the amount of time left.
Hope that helps

Fading In And Fading Out A Label Infinitively With separate `text`

I have a label in xCode that I want to infinitely fade between a word and then the current time.
I've had a look online and can find a single fade but am new to Swift so I'm struggling to adapt this to my need.
Any help would be amazing, Thanks!
Here is an example of how to do this:
#IBOutlet weak var label: UILabel!
#IBAction func fade_out(sender: AnyObject) {
fade(label)
}
#IBAction func reset(sender: AnyObject) {
label.text = "label"
}
func fade(label : UILabel) {
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
label.alpha = 0.0
}, completion: nil)
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
label.alpha = 1.0
label.text = String(NSDate())
}, completion: nil)
}
What I do is that I fade out the label first with UIViewAnimationOptions.CurveEaseOut and then fade it in again with todays date with UIViewAnimationOptions.CurveEaseIn.
Here is a test project I created for you so that you can see how I have done it.
Edit
To do it infinity, you can do it like this and without a button
var timer = NSTimer()
var b = false
override func viewDidLoad() {
super.viewDidLoad()
self.timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(ViewController.fade), userInfo: nil, repeats: true)
}
func fade() {
if b{
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.label.alpha = 0.0
self.label.text = "Label"
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.label.alpha = 1.0
self.label.text = String(NSDate())
}, completion: nil)
b = false
}
else{
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.label.alpha = 0.0
self.label.text = String(NSDate())
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.label.alpha = 1.0
self.label.text = "Label"
}, completion: nil)
b = true
}
}
A much Elegant way will be to use CAAnimation ->
class YourClass: UIViewController , ..{
#IBOutlet weak var fadingLabel: UILabel!
var a = true
override func viewDidLoad() {
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.swapText), userInfo: nil, repeats: true)
let anim : CABasicAnimation = CABasicAnimation(keyPath: "opacity")
anim.fromValue = 1
anim.toValue = 0
anim.duration = 0.5
anim.autoreverses = true
anim.repeatCount = Float.infinity
fadingLabel.layer.addAnimation(anim, forKey: "flashOpacity")
}
func swapText(){
if a == true{
fadingLabel.text = String(NSDate())
a = false
}else{
fadingLabel.text = String("My Text")
a = true
}
}
}
All you gotta do now is manipulate anim.duration and timeInterval in NSTimer() accordingly ...

How to make a button flash or blink?

I am trying to change a button's color (just a flash/blink) to green when a scan is correct and red when there's a problem. I am able to do this with a view like so
func flashBG(){
UIView.animateWithDuration(0.7, animations: {
self.view.backgroundColor = UIColor.greenColor()
})
}
But with a button it stays green
func flashBtn(){
UIButton.animateWithDuration(0.5, animations: {
self.buttonScan.backgroundColor = UIColor.greenColor()
})
}
I have created the button by code
func setupScanButton() {
let X_Co = (self.view.frame.size.width - 100)/2
let Y_Co = (self.viewForLayer.frame.size.height + 36/2)
buttonScan.frame = CGRectMake(X_Co,Y_Co,100,100)
buttonScan.layer.borderColor = UIColor.whiteColor().CGColor
buttonScan.layer.borderWidth = 2
buttonScan.layer.cornerRadius = 50
buttonScan.setTitle("Scan", forState: .Normal)
buttonScan.backgroundColor = UIColor.blueColor()
buttonScan.addTarget(self, action: "buttonScanAction", forControlEvents: .TouchUpInside)
buttonScan.setTitleColor(UIColor(red:255/255, green: 255/255, blue:255/255, alpha: 1), forState: UIControlState.Normal)
self.view.addSubview(buttonScan)
}
Should i call setupScanButton() again?
This should work in Swift 4
extension UIView{
func blink() {
self.alpha = 0.2
UIView.animate(withDuration: 1, delay: 0.0, options: [.curveLinear, .repeat, .autoreverse], animations: {self.alpha = 1.0}, completion: nil)
}
}
This will start and stop a flashing button onClick, if you only want to flash the button immediately just use the first statement.
var flashing = false
#IBAction func btnFlash_Clicked(sender: AnyObject) {
if !flashing{
self.buttonScan.alpha = 1.0
UIView.animateWithDuration(0.5, delay: 0.0, options: [.CurveEaseInOut, .Repeat, .Autoreverse, .AllowUserInteraction], animations: {() -> Void in
self.buttonScan.alpha = 0.0
}, completion: {(finished: Bool) -> Void in
})
flashing = true
}
else{
UIView.animateWithDuration(0.1, delay: 0.0, options: [.CurveEaseInOut, .BeginFromCurrentState], animations: {() -> Void in
self.buttonScan.alpha = 1.0
}, completion: {(finished: Bool) -> Void in
})
}
}
Swift 5.x version
An updated version with extension.
extension UIView {
func blink(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, alpha: CGFloat = 0.0) {
UIView.animate(withDuration: duration, delay: delay, options: [.curveEaseInOut, .repeat, .autoreverse], animations: {
self.alpha = alpha
})
}
}
To call the function:
button.blink() // without parameters
button.blink(duration: 1, delay: 0.1, alpha: 0.2) // with parameters
I hope that will solve your problem.
buttonScan.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 1.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
buttonScan.alpha = 0.0
}, completion: nil)
Swift 4:
I've maked an extension with some useful options:
extension UIButton {
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self.bounds.contains(point) ? self : nil
}
func blink(enabled: Bool = true, duration: CFTimeInterval = 1.0, stopAfter: CFTimeInterval = 0.0 ) {
enabled ? (UIView.animate(withDuration: duration, //Time duration you want,
delay: 0.0,
options: [.curveEaseInOut, .autoreverse, .repeat],
animations: { [weak self] in self?.alpha = 0.0 },
completion: { [weak self] _ in self?.alpha = 1.0 })) : self.layer.removeAllAnimations()
if !stopAfter.isEqual(to: 0.0) && enabled {
DispatchQueue.main.asyncAfter(deadline: .now() + stopAfter) { [weak self] in
self?.layer.removeAllAnimations()
}
}
}
}
First of all, I've overrided the hittest function to enabling the touch also when the button have the alpha equals to 0.0 (transparent) during the animation.
Then , all input vars have a default value so you can launch the blink() method without parameters
I've introduced also the enabled parameter to start or stop the animations on your button.
Finally, if you want you can stop animation after a specific time with the stopAfter parameter.
Usage:
yourButton.blink() // infinite blink effect with the default duration of 1 second
yourButton.blink(enabled:false) // stop the animation
yourButton.blink(duration: 2.0) // slowly the animation to 2 seconds
yourButton.blink(stopAfter:5.0) // the animation stops after 5 seconds.
Typical uses:
yourButton.blink(duration: 1.5, stopAfter:10.0)
// your code..
yourButton.blink()
// other code..
yourButton.blink(enabled:false)
You can try something like this:
extension UIView {
func blink() {
UIView.animateWithDuration(0.5, //Time duration you want,
delay: 0.0,
options: [.CurveEaseInOut, .Autoreverse, .Repeat],
animations: { [weak self] in self?.alpha = 0.0 },
completion: { [weak self] _ in self?.alpha = 1.0 })
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,Int64(2 * NSEC_PER_SEC)),dispatch_get_main_queue()){
[weak self] in
self?.layer.removeAllAnimations()
}
}
}
//MARK : Usage
yourButton.flash()
extension UIButton {
func flash() {
let flash = CABasicAnimation(keyPath: "opacity")
flash.duration = 0.5
flash.fromValue = 1
flash.toValue = 0.1
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
flash.autoreverses = true
flash.repeatCount = 3
layer.add(flash, forKey: nil)
}
}
Swift 3.0
func btnFlash_Clicked(sender: AnyObject) {
if !flashing{
callButton.alpha = 1.0
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.allowUserInteraction], animations: {() -> Void in
callButton.alpha = 0.5
}, completion: {(finished: Bool) -> Void in
})
flashing = true
}
else{
flashing = false
callButton.alpha = 0.5
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.allowUserInteraction], animations: {() -> Void in
callButton.alpha = 1.0
}, completion: {(finished: Bool) -> Void in
})
}
}
with UIViewPropertyAnimator and Swift 5
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0, options: [.curveLinear,.repeat], animations: {
UIView.setAnimationRepeatCount(3000)
self.buttonScan.alpha = 0.0
}, completion: {_ in })
Swift 3.0
func animateFlash() {
flashView.alpha = 0
flashView.isHidden = false
UIView.animate(withDuration: 0.3, animations: { flashView.alpha = 1.0 }) { finished in flashView.isHidden = true }
}
This UIView extension "blinks" a view and changes the background colour:
/**
Blinks a view with a given duration and optional color.
- Parameter duration: The duration of the blink.
- Parameter color: The color of the blink.
*/
public func blink(withDuration duration: Double = 0.25, color: UIColor? = nil) {
alpha = 0.2
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveEaseInOut], animations: {
self.alpha = 1.0
})
guard let newBackgroundColor = color else { return }
let oldBackgroundColor = backgroundColor
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveEaseInOut], animations: {
self.backgroundColor = newBackgroundColor
self.backgroundColor = oldBackgroundColor
})
}
You would then use as follows:
buttonScan.blink(color: .green)
myButton.alpha = 0.7
UIView.animate(withDuration: 0.3,
delay: 1.0,
options: [UIView.AnimationOptions.curveLinear, UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse],
animations: { myButton.alpha = 1.0 },
completion: nil)
Another smoothly animating version for Swift 5:
public extension UIView {
func blink(duration: TimeInterval) {
let initialAlpha: CGFloat = 1
let finalAlpha: CGFloat = 0.2
alpha = initialAlpha
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .beginFromCurrentState) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.alpha = finalAlpha
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.alpha = initialAlpha
}
}
}
}

swift background color animation loop

I want the background color of my iOS app to change between four colors over x amount of seconds
This is what I have so far (it does exactly what I want when I specify just 2 colors)
I also need the animation to run in a loop infinitely.
ViewController.swift
UIView.animateWithDuration(X.0, animations: {
// Color 1
self.view.backgroundColor = UIColor(rgba)
// Color 2
self.view.backgroundColor = UIColor(rgba)
// Color 3
self.view.backgroundColor = UIColor(rgba)
// Color 4
self.view.backgroundColor = UIColor(rgba)
})
Try this out:
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.blackColor()
}) { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.greenColor()
}, completion: { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.grayColor()
}, completion: { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.redColor()
}, completion:nil)
})
})
}
In case you want a continuous repeating animation, try this out:
UIView.animate(withDuration: 2, delay: 0.0, options:[UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse], animations: {
self.view.backgroundColor = UIColor.black
self.view.backgroundColor = UIColor.green
self.view.backgroundColor = UIColor.darkGray
self.view.backgroundColor = UIColor.red
}, completion: nil)
Below code helps to keep enable user interaction with animated view. A random color generation (use in case its needed).
UIView.animateWithDuration(7, delay: 1, options:
[UIViewAnimationOptions.AllowUserInteraction,
UIViewAnimationOptions.Repeat,
UIViewAnimationOptions.Autoreverse],
animations: {
self.yourView.backgroundColor = self.randomColor()
self.yourView.backgroundColor = self.randomColor()
}, completion:nil )
func randomColor() -> UIColor {
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue,
alpha: 1.0)
}
You have to use NSTimer:
let timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "update", userInfo: nil, repeats: true)
func update() {
let nextCollor = getNextColor()
UIView.animateWithDuration(X.0, animations: {
self.view.backgroundColor = nextCollor
})
}
func getNextColor() -> UIColor {
let currentColor = self.view.backgroundColor
if currentColor == smaple1 {
return UIColor.redColor()
} else if currentColor == smaple2 {
return UIColor.grayColor()
} else {
return UIColor.whiteColor()
}
}
NSTimer.scheduledTimerWithTimeInterval runs your code every 5 seconds
PS: do not forget invalidate timer when you done with it. Just call timer.invalidate() for it. Otherwise you get a crash.
Swift 5
override func viewWillAppear(_ animated: Bool) {
self.customButton.backgroundColor = .red
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse], animations: {
self.customButton.backgroundColor = .none
}, completion: nil)
}
i had a dynamic / unknown amount of colors to cycle through, so i refactored the Swift 5 answer a bit:
Swift 5
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse]) {
// colors is the dynamic [UIColor] array previously defined
for color in colors {
self.colorView.backgroundColor = color
}
} completion: { (finished) in
}
this will nicely cycle through the colors with a fade-style animation -- and with the .autoreverse option, it won't "snap" to the starting color.

Resources