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
}
}
}
}
Related
I'd like to animate a UIImageView left and right. My goal is quite simple:
I have an image, I'd like to rotate it slightly to the left with an angle of .pi/8 and then from that rotated position, rotate it back to -pi/8. I want to repeat these rotations three times and stop. The image has to be in a non-rotated state initially.
I was able to achieve it by doing this:
UIView.animate(withDuration: 0.3, delay: 0.4, options: [], animations: {
self.imageView.rotate(angle: .pi/9)
}, completion: { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.imageView.rotate(angle: -.pi/7)
}, completion: { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.imageView.rotate(angle: .pi/9)
}, completion: { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.imageView.rotate(angle: -.pi/7)
}, completion: { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.imageView.rotate(angle: .pi/9)
}, completion: { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.imageView.rotate(angle: -.pi/7)
}, completion: {_ in
UIView.animate(withDuration: 0.3, delay: 0.0, options: [], animations: {
self.giftImageView.rotate(angle: .pi/10.5)
})})})})})})})})
However, I believe there is a simpler way to do it but I can't figure it out and the research isn't leading to any simpler solution. After some trial and error, I got to .pi/10.5 to have the image view centered and to not look tilted.
I'd much appreciate your help for a simpler way, thanks!
here is rotation extension function
extension UIView {
func rotation(isClockWise:Bool) {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
if isClockWise {
rotation.toValue = NSNumber(value: Double.pi / 8)
} else {
rotation.toValue = NSNumber(value: -Double.pi / 8)
}
rotation.duration = 1 // 1 second duration change it to 0.3 or whatever
rotation.isCumulative = true
rotation.repeatCount = 3
self.layer.add(rotation, forKey: "rotationAnimation")
}
}
then you need to do some work in controller
class ViewController:UIViewController {
var timer = Timer()
var isClockWise = false
var rotationCount = 0
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .orange
timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(rotationHandle), userInfo: nil, repeats: true)
}
#objc
func rotationHandle() {
rotationCount += 1
if isClockWise {
imageView.rotation(isClockWise: true)
} else {
imageView.rotation(isClockWise: false)
}
isClockWise.toggle()
if rotationCount == 6 {
timer.invalidate()
imageView.layer.removeAllAnimations()
imageView.transform = .identity
}
}
}
I have below code for changing the value of object like text,image , ... with Effect. and it work:
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.lblCityTemp.alpha = 0.0
self.imgCityIcon.alpha = 0.0
}, completion: {
(finished: Bool) -> Void in
//Once the label is completely invisible, set the text and fade it back in
self.imgCityIcon.image = UIImage(named: icon)
self.lblCityTemp.text = "\(temp)°"
// Fade in
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.lblCityTemp.alpha = 1.0
self.imgCityIcon.alpha = 1.0
}, completion: nil)
})
Now I want make a extension of this code that I use for all my object that I write below code:
extension NSObject {
func Fade (alphaOUT: Double,alphaIN: Double, input: Any) {
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.alpha = CGFloat(alphaOUT)
}, completion: {
(finished: Bool) -> Void in
// Fade in
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.alpha = CGFloat(alphaIN)
}, completion: nil)
})
}
}
But I have error on self word and I do not know how can I set value for every object like label, imageView , ....
How can I do this?
Is the extension is best way or no?
If no, so what is the best way?
The whole idea should be take some UIView and animate it. Then you need extension of UIView since this class has properties that you need to.
extension UIView { ... }
Anyway, what now if you need to do something when animation ends? Then you'll need completion handler parameter for your method
func fade(..., completion: #escaping () -> Void = { }) {
... which will be called after the first animation ends.
Next suggestions:
You can say that alpha parameters should be of type CGFloat, then you don’t have to convert Double to CGFloat
Also, what is input? I think you won't need it with this solution
Method name should start with small capital letter and for naming parameters you should use camelCase
extension UIView {
func fade(alphaOut: CGFloat, alphaIn: CGFloat, completion: #escaping () -> Void = { }) {
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
self.alpha = alphaOut
}, completion: { _ in
completion() // this is called when animation ends
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.alpha = alphaIn
})
})
}
}
Then when you need to call it, change what you need after animation ends in completion closure
imgCityIcon.fade(alphaOut: ___, alphaIn: ___) {
self.imgCityIcon.image = UIImage(named: icon)
}
Note that you're using old syntax for some stuff:
For example UIViewAnimationOptions.curveEaseIn has been renamed to UIView.AnimationOptions.curveEaseIn. I would suggest you to start using newer versions of Swift
You need to implement an extension for UIView instead of NSObject.
since NSObject doesn't have any property like alpha you will get an
error.
Coding Example:
extension UIView {
func Fade (alphaOUT: Double,alphaIN: Double, input: Any) {
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.alpha = CGFloat(alphaOUT)
}, completion: {
(finished: Bool) -> Void in
// Fade in
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.alpha = CGFloat(alphaIN)
}, completion: nil)
})
}
}
class AnimationHelper {
class func Fade (alphaOUT: Double,alphaIN: Double, input: Any , yourLabel: UILabel, yourTextField: UITextField) {
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
yourLabel.alpha = CGFloat(alphaOUT)
yourTextField.alpha = CGFloat(alphaOUT)
}, completion: {
(finished: Bool) -> Void in
// Fade in
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
// Access your text field and label here.
}, completion: nil)
})
}
}
Here is my Swift Code which i am using to show 3 different view in single cell.
func animateBothObjects() {
weak var weakSelf = self
UIView.animate(withDuration: 1.0, delay: 2.0, options: [.curveEaseIn, .allowUserInteraction], animations: {() -> Void in
self.infoView.alpha = 0.0
self.offerImage.alpha = 1.0
self.businessOwnerImage.alpha = 0.0
}, completion: {(finished: Bool) -> Void in
if !self.isAccountFeed && !finished {
return
}
UIView.animate(withDuration: 1.0, delay: 2.0, options: [.curveEaseIn, .allowUserInteraction], animations: {() -> Void in
self.infoView.alpha = 0.0
self.offerImage.alpha = 0.0
self.businessOwnerImage.alpha = 1.0
}, completion: {(finished: Bool) -> Void in
if !self.isAccountFeed && !finished {
return
}
UIView.animate(withDuration: 1.0, delay: 2.0, options: [.curveEaseIn, .allowUserInteraction], animations: {() -> Void in
self.infoView.alpha = 1.0
self.offerImage.alpha = 0.0
self.businessOwnerImage.alpha = 0.0
}, completion: {(finished: Bool) -> Void in
if !self.isAccountFeed && !finished {
return
}
weakSelf!.animateBothObjects()
})
})
})
}
this function works fine but frame per second and CPU usage is very high.
I was wondering if somehow we can optimize this animation using .repeat and .autoreverse
When i try to do that the animation doesn't work in sync. I need to animate one view after another
1> view 1 alpha 0 to 1
2> view 2 alpha 0 to 1
3> view 3 alpha 0 to 1
and than repeat the cycle.
Following Code works better in terms of CPU % and Frame per seconds but the animations are not in sync.
self.infoView.alpha = 0.0
self.offerImage.alpha = 0.0
self.businessOwnerImage.alpha = 0.0
UIView.animate(withDuration: 2.0, delay: 0.0, options: [.transitionCrossDissolve,.repeat,.autoreverse], animations: {
self.infoView.alpha = 1.0
}) { (success) in
//self.infoView.alpha = 0.0
print("animation completed as per Xcode")
}
UIView.animate(withDuration: 2.0, delay: 2.0, options: [.transitionCrossDissolve, .repeat,.autoreverse], animations: {
self.offerImage.alpha = 1.0
}) { (success) in
//self.offerImage.alpha = 0.0
}
UIView.animate(withDuration: 2.0, delay: 4.0, options: [.transitionCrossDissolve, .repeat,.autoreverse], animations: {
self.businessOwnerImage.alpha = 1.0
}) { (success) in
//self.businessOwnerImage.alpha = 0.0
}
I have a label that is initially positioned at the center of the screen. It currently transitions from the the center to the right end of the screen then autoreverses back to its position. I'd like to have it begin another animateWithDuration so that it continues from the center return to the left position of the screen then autoreverse back to the position and sequentially loop from there on after.
I have already attempted and successfully made the first half work but I'm not sure how to continue to the second portion where it begins the center->left transition and loop.
Swift 2.0 Code:
func animateRight()
{
UIView.animateWithDuration(1.0, delay: 0.0, options: [ .Autoreverse, .CurveEaseInOut], animations: {
label.center.x = self.view.frame.width/2
}, completion: { finished in
if finished {
label.frame.origin.x = 0.0
animateLeft()
}
})
}
func animateLeft()
{
UIView.animateWithDuration(1.0, delay: 0.0, options: [ .Autoreverse, .CurveEaseInOut], animations: {
label.frame.origin.x = (self.view.frame.width/2) * -1
}, completion: { finished in
if finished {
label.center.x = self.view.frame.width/2
animateRight()
}
})
}
// Start process
animateRight()
You should call the same animate with duration method you create for animating to right and call in completion. Something like this:
func animateRight()
{
UIView.animate(withDuration: 1.0, delay: 0.0, options: [], animations: {
self.label.center.x = self.view.frame.width
}, completion: { finished in
if finished {
self.animateLeft()
}
})
}
func animateLeft()
{
UIView.animate(withDuration: 2.0, delay: 0.0, options: [ .autoreverse, .repeat, .curveEaseInOut, .beginFromCurrentState], animations: {
self.label.frame.origin.x = 0.0
}, completion: nil)
}
Call defaultSetup to start
When animationRight end, it will call animationLeft
When animationLeft end, it will call animationRight again to run animation loop
Here is the code:
func defaultSetup(){
self.animationRight()
}
func animationRight(){
let transform = CGAffineTransform(scaleX: 1.05, y: 1.05).rotated(by: .pi/180)
UIView.animate(withDuration: 3.0, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 3.0, options: [.allowUserInteraction, .repeat], animations: { [weak self] in
guard let `self` = self else { return }
self.vRight.transform = transform
}, completion: { [weak self] (done) in
guard let `self` = self else { return }
self.vRight.transform = .identity
self.animationLeft()
})
}
func animationLeft(){
let transform = CGAffineTransform(scaleX: 1.05, y: 1.05).rotated(by: .pi/180)
UIView.animate(withDuration: 3.0, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 3.0, options: [.allowUserInteraction], animations: { [weak self] in
guard let `self` = self else { return }
self.vLeft.transform = transform
}, completion: { [weak self] (done) in
guard let `self` = self else { return }
self.vLeft.transform = .identity
self.animationRight()
})
}
I want to rotate an image view for 360 degrees indefinitely.
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
self.view.transform = CGAffineTransform(rotationAngle: 6.28318530717959)
}, completion: nil)
How can I do it?
UPDATE Swift 5.x
// duration will helps to control rotation speed
private func rotateView(targetView: UIView, duration: Double = 5) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: .pi)
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 2.x way to rotate UIView indefinitely, compiled from earlier answers:
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
targetView.transform = CGAffineTransformRotate(targetView.transform, CGFloat(M_PI))
}) { finished in
self.rotateView(targetView, duration: duration)
}
}
UPDATE Swift 3.x
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(M_PI))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 3.0
let imgViewRing = UIImageView(image: UIImage(named: "apple"))
imgViewRing.frame = CGRect(x: 0, y: 0, width: UIImage(named: "apple")!.size.width, height: UIImage(named: "apple")!.size.height)
imgViewRing.center = CGPoint(x: self.view.frame.size.width/2.0, y: self.view.frame.size.height/2.0)
rotateAnimation(imageView: imgViewRing)
self.view.addSubview(imgViewRing)
This is the animation logic
func rotateAnimation(imageView:UIImageView,duration: CFTimeInterval = 2.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = duration
rotateAnimation.repeatCount = .greatestFiniteMagnitude
imageView.layer.add(rotateAnimation, forKey: nil)
}
You can check output in this link
Use this extension to rotate UIImageView 360 degrees.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.duration = duration
if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate? {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
Than to rotate UIImageView simply use this method
self.YOUR_SUBVIEW.rotate360Degrees()
This one work for me in Swift 2.2:
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = 360 * CGFloat(M_PI/180)
let innerAnimationDuration : CGFloat = 1.0
rotationAnimation.duration = Double(innerAnimationDuration)
rotationAnimation.repeatCount = HUGE
self.imageView.addAnimation(rotationAnimation, forKey: "rotateInner")
I would stick it in a function like rotateImage() and in the completion code just call rotateImage() again. I think you should use M_PI (or the swift equivalent) for the rotation amount, though.
Updated for Swift 3:
I made an extension to UIView and included the following function within:
func rotate(fromValue: CGFloat, toValue: CGFloat, duration: CFTimeInterval = 1.0, completionDelegate: Any? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: >"transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration
if let delegate: Any = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)
}
You can then call the function via (on a UIView I made into a button for example) :
monitorButton.rotate(fromValue: 0.0, toValue: CGFloat(M_PI * 2), completionDelegate: self)
Hope this helps!
I think what you really want here is to use a CADisplayLink. Reason being that this would be indefinitely smooth versus using completion blocks which may cause slight hiccups and are not as easily cancelable. See the following solution:
var displayLink : CADisplayLink?
var targetView = UIView()
func beginRotation () {
// Setup display link
self.displayLink = CADisplayLink(target: self, selector: #selector(onFrameInterval(displayLink:)))
self.displayLink?.preferredFramesPerSecond = 60
self.displayLink?.add(to: .current, forMode: RunLoop.Mode.default)
}
func stopRotation () {
// Invalidate display link
self.displayLink?.invalidate()
self.displayLink = nil
}
// Called everytime the display is refreshed
#objc func onFrameInterval (displayLink: CADisplayLink) {
// Get frames per second
let framesPerSecond = Double(displayLink.preferredFramesPerSecond)
// Based on fps, calculate how much target view should spin each interval
let rotationsPerSecond = Double(3)
let anglePerSecond = rotationsPerSecond * (2 * Double.pi)
let anglePerInterval = CGFloat(anglePerSecond / framesPerSecond)
// Rotate target view to match the current angle of the interval
self.targetView.layer.transform = CATransform3DRotate(self.targetView.layer.transform, anglePerInterval, 0, 0, 1)
}
Avoiding the completion closure with recursive calls!
Bit late to this party, but using UIView keyFrame animation & varying stages of rotation for each keyFrame, plus setting the animation curve works nicely. Here's an UIView class function -
class func rotate360(_ view: UIView, duration: TimeInterval, repeating: Bool = true) {
let transform1 = CGAffineTransform(rotationAngle: .pi * 0.75)
let transform2 = CGAffineTransform(rotationAngle: .pi * 1.5)
let animationOptions: UInt
if repeating {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue
} else {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue
}
let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [keyFrameAnimationOptions, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.375) {
view.transform = transform1
}
UIView.addKeyframe(withRelativeStartTime: 0.375, relativeDuration: 0.375) {
view.transform = transform2
}
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
view.transform = .identity
}
}, completion: nil)
}
Looks pretty gnarly, with the weird rotation angles, but as the op & others have found, you can't just tell it to rotate 360
Try this one it works for me, i am rotating image for once
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformRotate(self.imgViewReload.transform, CGFloat(M_PI))
}, completion: {
(value: Bool) in
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformIdentity
}, completion: nil)
})
you can use this function ... just give it the view and the duration
it has two animations the the first rotates the view 180° and the other one rotates it to 360° then the function calls itself which allows it to continue the rotation animation infinitely
func infinite360Animation(targetView: UIView, duration: Double) {
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
} completion: { (_) in
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
} completion: { (_) in
self.infinite360Animation(targetView: targetView, duration: duration)
}
}
}
You can then call the function like this
infinite360Animatio(targetView: yourView, duration: 3)
On your tabBarController, make sure to set your delegate and do the following didSelect method below:
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
if let selectedItem:UITabBarItem = tabBarController.tabBar.selectedItem {
animated(tabBar: tabBarController.tabBar, selectedItem: selectedItem)
}
}
fileprivate func animated(tabBar: UITabBar, selectedItem:UITabBarItem){
if let view:UIView = selectedItem.value(forKey: "view") as? UIView {
if let currentImageView = view.subviews.first as? UIImageView {
rotate(imageView: currentImageView, completion: { (completed) in
self.restore(imageView: currentImageView, completion: nil)
})
}
}
}
fileprivate func rotate(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
}, completion: {
(value: Bool) in
completion?(value)
})
}
fileprivate func restore(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.identity
}, completion: {
(value: Bool) in
completion?(value)
})
}