Rotating button animation doesn't work - ios

I have a button, when it's tapped, it should rotate itself, here's my code:
#IBAction func calculateButtonTapped(_ sender: UIButton) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.speed = 3.0
rotateAnimation.repeatCount = 6000
calculateButton.layer.add(rotateAnimation, forKey: nil)
DispatchQueue.main.async {
self.openCircle(withCenter: sender.center, dataSource: self.calculator!.iterateWPItems())
self.calculateButton.layer.removeAllAnimations()
}
}
However, sometimes when I tap the button, it immediately goes back to normal state then rotates, sometimes the button changes to dark selected state, and doesn't animate at all, tasks after the animates will get finished. If I don't stop the animation, it starts after openCircle is finished.
What could be the cause?

You're not setting duration of your animation.
Replace this
rotateAnimation.speed = 3.0
with this
rotateAnimation.duration = 3.0
#alexburtnik and it's ok to block the main thread
No, it's not ok. You should add a completion parameter in openCircle method and call it whenever it's animation (or whatever) is finished. If you block main thread, you will have a frozen UI, which is strongly discouraged.
If you're unsure that calculateButtonTapped is called on main thread, you should dispatch first part of your method as well. Everything related to UI must be done on the main thread.
It should look similar to this:
#IBAction func calculateButtonTapped(_ sender: UIButton) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.duration = 3.0
rotateAnimation.repeatCount = .infinity //endless animation
calculateButton.layer.add(rotateAnimation, forKey: nil)
self.openCircle(
withCenter: sender.center,
dataSource: self.calculator!.iterateWPItems(),
completion: {
self.calculateButton.layer.removeAllAnimations()
})
}
func openCircle(withCenter: CGPoint, dataSource: DataSourceProtocol, completion: (()->Void)?) {
//do your staff and call completion when you're finished
//don't block main thread!
}

Try this out in order to rotate a button that is clicked by connecting the button to the action on a storyboard. You can of course call this function by passing any UIButton as the sender!
#IBAction func calculateButtonTapped(_ sender: UIButton) {
guard (sender.layer.animation(forKey: "rotate") == nil) else { return }
let rotationDuration: Float = 3.0
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.toValue = Float.pi * rotationDuration
animation.duration = CFTimeInterval(rotationDuration)
animation.repeatCount = .infinity
sender.layer.add(animation, forKey: "rotate")
}
Change the rotationDuration to whatever time length you want for a full rotation. You could also adjust the function further to take that as an argument.
Edit: Added a guard statement so that the rotations don't keep adding up every time that the button is tapped.

Thanks to everybody for answering, I found the solution myself after a crash course on multithreading, the problem is I blocked the main thread with openCircle method.
Here's the updated code:
#IBAction func calculateButtonTapped(_ sender: UIButton) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.speed = 3.0
rotateAnimation.repeatCount = .infinity
DispatchQueue.global(qos: .userInitiated).async {
self.openCircle(withCenter: sender.center, dataSource: self.calculator!.iterateWPItems()){}
DispatchQueue.main.sync {
self.calculateButton.layer.removeAllAnimations()
}
}
self.calculateButton.layer.add(rotateAnimation, forKey: nil)
}

Related

Get UIVIew from CAAnimation

I am trying to get UIView object from CAAnimation. I have implemented the following CAAnimationDelegate method
public func animationDidStop(_ animation:CAAnimation, finished:Bool) {
// Need respective View from "animation:CAAnimation"
}
This class will be performing multiple animations with different views. So I need to find out which View's animation is completed in this delegate method. Please guide me if there is any possibility to get the view from this animation.
As matt suggested here is the way you can find which animation has been completed.
First of all you need to add different key value to your animation when you are creating it like shown below:
let theAnimation = CABasicAnimation(keyPath: "opacity")
theAnimation.setValue("animation1", forKey: "id")
theAnimation.delegate = self
let theAnimation2 = CABasicAnimation(keyPath: "opacity")
theAnimation2.setValue("animation2", forKey: "id")
theAnimation2.delegate = self
And in animationDidStop method you can identify animations:
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if let val = anim.value(forKey: "id") as? String {
switch val {
case "animation1":
print("animation1")
case "animation2":
print("animation2")
default:
break
}
}
}
I have taken THIS answer and converted Objective c code to swift with switch case.
I just use tag property in UIView
animatedView.tag = 10
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for myView in view.subviews {
if myView.tag == 10 {
myView.removeFromSuperview()
return
}
}
}

[Big help]Custom transition animation not triggered when VC present

:)
I want make some change to source code for 17-custom-presentation-controller to make custom transition animation,all my change is in class PopAnimator.
all my code is in here : https://github.com/hopy11/TransitionTest
This is my changes:
add a new instance variable in PopAnimator to save transitionContext:
var ctx:UIViewControllerContextTransitioning!
I rewrite the method:
animateTransition(using transitionContext: UIViewControllerContextTransitioning)
to this :
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
//save transitionContext
ctx = transitionContext
containerView.addSubview(toView)
let animation = CATransition()
animation.duration = duration / 2
animation.type = "cube"
//use type kCATransitionReveal is not work too ...
//animation.type = kCATransitionReveal
animation.subtype = kCATransitionFromLeft
animation.delegate = self
containerView.layer.add(animation, forKey: nil)
}
3.Last I make class PopAnimator confirm CAAnimationDelegate delegate, and add the new method:
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if ctx != nil{
//make transition complete
ctx.completeTransition(true)
dismissCompletion?()
}
}
When run the App,the transition animation is look good when detailsVC dismiss,but nothing happened when presenting detailsVC!
You can see the demo above : when user tap the bottom left green view,it present detailsVC,but no animation happened!!! but when dismiss detailsVC,the animation work fine!
What’s wrong with it???
and how to fix it???
all my code is in here : https://github.com/hopy11/TransitionTest
thanks a lot! :)

changing opacity of button when clicked xcode / swift

I have an UIButton set up with an image and by default when the user presses the button, the image is reduced to around 30% opacity.
I am wondering how to prevent this from happening and how to set the opacity to whatever I need it to be.
To add on the viewController, if you want to change opacity and time delay programmatically.
#IBAction func keyPressed(_ sender: UIButton) {
playSound(soundName: sender.currentTitle!)
//Reduces the sender's (the button that got pressed) opacity to half.
sender.alpha = 0.5
//Code should execute after 0.2 second delay.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
//Bring's sender's opacity back up to fully opaque.
sender.alpha = 1.0
}
}
func playSound(soundName: String) {
let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
Swift 5
You can do this by using DispatchQueue. What's more, to make the transition "smooth" use UIView.animate.
Just change the alpha parameter.
#IBAction func keyPressed(_ sender: UIButton) {
sender.alpha = 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 ) {
sender.alpha = 1.0
}
}
Smooth change of the parameter.
#IBAction func keyPressed(_ sender: UIButton) {
UIView.animate(withDuration: 0.3) {
sender.alpha = 0.5
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 ) {
UIView.animate(withDuration: 0.3) {
sender.alpha = 1.0
}
}
}
If you want to change the opacity you should use this code below. The alpha is basically the opacity. You can actually change the bottom part time, like how long you want it to be dimmed, you can also change sender.alpha value, like how dim you want it to be.
#IBAction func keyPressed(_ sender: UIButton)
{
// Reduces the sender's (the button that got pressed) opacity to half.
sender.alpha = 0.5
// Code should execute after 0.2 second delay.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
// Bring's sender's opacity back up to fully opaque.
sender.alpha = 1.0
}
}
I don't like a lot of these answers because they shortcut making the user think the button changes color when they tap it but only mimic a functionality apple has already provided for us. In my mind the optimal functionality would allow the button to change its opacity only when pressed and then revert when not selected. Try this out in Swift 5:
1) Create a new swift file called SomeCustomBtn
2) Insert this code:
import UIKit
class SomeCustomBtn: UIButton {
override open var isHighlighted: Bool {
didSet {
alpha = isHighlighted ? 0.5 : 1.0
}
}
}
3) Add your custom class to your buttons and iOS will automatically change your alpha based on the attribute isHighlighted!
To add on to AtWork, if you want to change the opacity programmatically at any time.
button.alpha = 0.30 // Make sure to use CGFloat literals
button.alpha = 1
button.alpha = 0
Easiest way:
#IBAction func keyPressed(_ sender: UIButton) {
sender.alpha = 0.5
}
//Reduces the opacity of the Button to half (the selected Button)
sender.alpha = 0.5
//this line of code will help you to delay the opacity to the selected seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
//This code brings sender's opacity back to fully opaque.
sender.alpha = 1.0
}
or one easy way is without using any DispatchQueue is this:
#IBAction func KeyDownPressed(_ sender: UIButton) {
sender.alpha = 0.5
}
#IBAction func keyPressed(_ sender: UIButton) {
sender.alpha = 1
playSound(col : sender.currentTitle!)
}
Please Note: Set Event of action of func KeyDownPressed to Touch Down
You can just simply set adjustImageWhenHighlighted to No.
button.adjustsImageWhenHighlighted = NO;
Let me know if it didn't work for you.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
var player: AVAudioPlayer!
#IBAction func keyPressed(_ sender: UIButton) {
playAudio(sound: sender.title(for: .normal)!)
sender.alpha = 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {sender.alpha = 1}
}
func playAudio(sound: String) {
let url = Bundle.main.url(forResource: sound, withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}

swift stop 360 degree animation

in my swift 2 app, i have this extension:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 2.5, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
with this code, i can rotate an image 360 degree.
now i would like to stop this animation directly after i pressed on a button.
in my view controller is an action for my button. if i press this button, the following value will set:
self.shouldStopRotating = true
and i have this code part in the same vc, too:
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if self.shouldStopRotating == false {
self.LoadingCircle.rotate360Degrees(completionDelegate: self)
}
}
the image will stop after i pressed the button, but it will stop after the animation will be finished (after 360 degrees) - but this is to late.
the image have to stop rotating directly on the actual position after i press the button
Try to add this when the button that stops the animation is presse:
self.LoadingCircle.layer.removeAllAnimations()
let currentLayer = self.LoadingCircle.layer.presentationLayer();
let currentRotation = currentLayer?.valueForKeyPath("transform.rotation.z")?.floatValue;
let rotation = CGAffineTransformMakeRotation(CGFloat(currentRotation!));
self.LoadingCircle.transform = rotation;

Rotation animation of multiple UIButtons, how can animationDidStop know which stopped?

I'm trying to make four UIButtons rotate in Swift. I got this:
import UIKit
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 * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
But I need it to repeat. In my UIViewController I used the animationDidStop function but how can I know which of the four animation triggers it? It has a parameter called CAAnimation but I cannot compare it to anything. Any suggestion?
Re-write your method using CATransition and use the solution from this question:
How to identify CAAnimation within the animationDidStop delegate?

Resources