Can't stop UIView animation in swift - ios

This is my animation code:
UIView.animate(withDuration: 1, delay: 1, options: [.repeat], animations: {
self.micButtonOuterRing?.frame = CGRect(x: (self.originalOuterRingFrame?.origin.x)! - 30, y: (self.originalOuterRingFrame?.origin.y)! - 30, width: (self.originalOuterRingFrame?.width)! + 60, height: (self.originalOuterRingFrame?.height)! + 60)
self.micButtonOuterRing?.alpha = 0
self.micButtonOuterRing?.layer.cornerRadius = (self.micButtonOuterRing?.frame.width)! / 2
}, completion: { _ in
self.micButtonOuterRing?.frame = self.originalOuterRingFrame!
self.micButtonOuterRing?.alpha = 1
})
I am trying to stop it when I press a button so I run the following:
#IBAction func micButtonPressed(_ sender: Any) {
micButtonOuterRing?.layer.removeAllAnimations()
}
But the animation keeps going. I know the micButtonPressed function works because I added a print statement and it worked. Can somebody help?

Use UIPropertyAnimator instead; its designed to let you pause, stop, reverse, etc. and the syntax is almost exactly the same as the old UIViewAnimation Methods.

Related

UIView animation in completion block starts with displacement

I wrote a simple animation chain with two animations using UIView.animate, but the second animation in completion block starts not exactly from where the first animation ens so I have strange displacement to the right. Anyone could help? Maybe I did not fully understand tranfrorm property.
UIView.animate(withDuration: 3, animations: {
self.redView.transform = self.redView.transform.translatedBy(x: 100, y: 0)
}) { (_) in
UIView.animate(withDuration: 2, animations: {
self.redView.transform = self.redView.transform.scaledBy(x: 2, y: 2)
})
}
My redView should be moved to right on 100 and then from the same place became twice as large. But before second animation there is displacement to the right. I have no ideas about why this happens.
Thanks!
Gif with this issue:
Not sure what's your intention but I'd animate the frame in the first block:
let initialFrame = redView.frame
UIView.animate(withDuration: 3, animations: {
self.redView.frame = initialFrame.offsetBy(dx: 100, dy: 0)
}) { (_) in
UIView.animate(withDuration: 2, animations: {
self.redView.transform = self.redView.transform.scaledBy(x: 2, y: 2)
})
}

How to define an animation?

I'm looking for a way to let a variable equal an animation. So that later I can refer to it and change the speed of the animation.
// Image Animation
UIView.animate(withDuration: 2.5, delay: 0.8, options: [.repeat, .curveLinear], animations: {
self.image.frame = CGRect(x: 250, y: 1200, width: 45, height: 45)
}, completion: nil)
// Function Speed Check
func speedcheck() {
if score > 25 {
Image.stopAnimating()
// Image Animation
UIView.animate(withDuration: 1.0, delay: 0.8, options: [.repeat, .curveLinear], animations: {
self.Image.frame = CGRect(x: 250, y: 1200, width: 45, height: 45)
}, completion: nil)
UIView.commitAnimations()
}
}
"Image.stopAnimating()"
As shown in the example, this line does not stop the animation that was previously executed above, because it does not know what to cease. Therefore, I'm wondering how to define the initial animation, so from there I can increase the speed.
What's the best way to speed up an animation when the score is greater than 25?
Any help would be appreciated.
You actually don't need to assign the animation to a variable. I don't think that is even possible.
By speeding up the animation, you mean reducing the duration of the animation.
To do that just hold the animationDuration value in a global variable say animationSpeed.
Just use this animationSpeed variable value in the UIView.animate() method for the duration param.
Declare a global speed param:
var animationSpeed = TimeInterval.init(5.0)
Use this in your UIView.animate() method:
UIView.animate(withDuration: animationSpeed) {
}
Now you can change this animationSpeed param value when a condition is satisfied:
if condition {
self.animationSpeed = TimeInterval.init(2.5)
}
When you give the animation options as .repeat, the values captured initially while entering the animation block will be used throughout the animation. This is not something that we want. Use something like this
func animateView() {
UIView.animate(withDuration: self.speed, delay: 0.0, options: [.curveLinear], animations: {
// Your animation
}, completion: { flag in
if flag {
if speed > 25 {
self.speed = TimeInterval.init(2.0)
self.view.layer.removeAllAnimations()
}
self.animateView()
}
})
}

Animating a UIView's alpha in sequence with UIViewPropertyAnimator

I have a UIView that I want to reveal after 0.5 seconds, and hide again after 0.5 seconds, creating a simple animation. My code is as follows:
let animation = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 1
let transition = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 0
}
transition.startAnimation(afterDelay: 0.5)
}
animation.startAnimation()
When I test it out, nothing happens. I assume it's because they're both running at the same time, which would mean they cancel each other out, but isn't that what the "afterDelay" part should prevent?
If I run them separately, i.e. either fading from hidden to visible, or visible to hidden, it works, but when I try to run them in a sequence, it doesn't work.
My UIView is not opaque or hidden.
You can use Timer, and add appearing / hiding animations blocks on every timer tick to your UIViewPropertyAnimatorobject.
Here's a codebase:
#IBOutlet weak var timerBackground: UIImageView!
private var timer: Timer?
private var isShown = false
private var viewAnimator = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear)
override func viewDidLoad() {
super.viewDidLoad()
viewAnimator.addAnimations {
self.timerBackground.alpha = 1
}
viewAnimator.startAnimation()
isShown = true
self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.startReversedAction), userInfo: nil, repeats: true)
}
func startReversedAction() {
// stop the previous animations block if it did not have time to finish its movement
viewAnimator.stopAnimation(true)
viewAnimator.addAnimations ({
self.timerBackground.alpha = self.isShown ? 0 : 1
})
viewAnimator.startAnimation()
isShown = !isShown
}
I've implemented the very similar behavior for dots jumping of iOS 10 Animations demo project.
Please, feel free to look at it to get more details.
Use UIView.animateKeyframes you'll structure your code nicely if you have complicated animations. If you'll use UIView animations nested within the completion blocks of others, it will probably result in ridiculous indentation levels and zero readability.
Here's an example:
/* Target frames to move our object to (and animate)
or it could be alpha property in your case... */
let newFrameOne = CGRect(x: 200, y: 50, width: button.bounds.size.width, height: button.bounds.size.height)
let newFrameTwo = CGRect(x: 300, y: 200, width: button.bounds.size.width, height: button.bounds.size.height)
UIView.animateKeyframes(withDuration: 2.0,
delay: 0.0,
options: .repeat,
animations: { _ in
/* First animation */
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameOne
})
/* Second animation */
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameTwo
})
/* . . . */
}, completion: nil)
What worked for me, was using sequence of UIViewPropertyAnimators. Here is example of my code:
let animator1 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator1.addAnimations {
smallCoin.transform = CGAffineTransform(scaleX: 4, y: 4)
smallCoin.center = center
}
let animator2 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator2.addAnimations {
center.y -= 20
smallCoin.center = center
}
let animator3 = UIViewPropertyAnimator(duration:10, curve: .easeIn)
animator3.addAnimations {
smallCoin.alpha = 0
}
animator1.addCompletion { _ in
animator2.startAnimation()
}
animator2.addCompletion { _ in
animator3.startAnimation()
}
animator3.addCompletion ({ _ in
print("finished")
})
animator1.startAnimation()
You can even add afterdelay attribute to manage speed of animations.
animator3.startAnimation(afterDelay: 10)

UIView block based animation weird behavior

I have got this very simple animation (case):
class ViewController: UIViewController {
var v: UIView!
var b = false
override func viewDidLoad() {
super.viewDidLoad()
self.v = UIView(frame: CGRect(x: 120, y: 250, width: 30, height: 30))
self.v.backgroundColor = .red
self.view.addSubview(self.v)
}
#IBAction func didTapButton(_ sender: UIButton) {
UIView.animate(withDuration: 3,
delay: 0,
options: [.beginFromCurrentState, .curveLinear],
animations: {
if self.b {
self.v.frame = CGRect(x: 120, y: 250, width: 30, height: 30)
} else {
self.v.frame = CGRect(x: 240, y: 250, width: 30, height: 30)
}
self.b = !self.b
},
completion: nil)
}
}
On each tap of the button the red view moves to left or right depending on current direction. The animation should start from the current position and to be linear.
However if I tap the button when the previous animation is already in progress then the red view does not start immediately to move in the opposite direction. It just freezes in the current position while the previous animation finishes and then starts moving. If I change the animation curve option from linear to easeOut and it works properly.
I am using iOS 10, Xcode 8.2.1
Any ideas why this happens?
When the animation is on, the UI components will not react to new changes. It will buffer the input and react once the animation is complete.
This is not related to the iOS or Xcode versions, but a normal behaviour.
I've found the answer.
I tis because of the additive animations in iOS 8 and above. Here is a very useful link which explains what actually happens and why the animation freezes.
http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/

UIView animation doesn't get stuck but UIButton does when blocking main thread

I have a simple animated rectangle on screen generated in code.
let rect = UIView()
self.view.addSubview(rect)
rect.backgroundColor = UIColor.black
rect.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
UIView.animate(withDuration: 2, delay: 0.0, options: [.autoreverse, .repeat], animations: {
rect.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
}, completion: { finished in
})
There are two buttons on screen: one triggers self.didPressStuckIt and another one triggers self.didPressDoNotStuckIt. My intension is to pause the animation by running simulated heavy computation on main thread, and do not pause the animation when I run the heavy computation in another thread.
#IBAction func didPressStuckIt(_ sender: UIButton) {
let runId = arc4random_uniform(10000000)
sleep(4)
statusLabel.text = "Status: didPressStuckIt Completed (\(runId))"
}
#IBAction func didPressDoNotStuckIt(_ sender: UIButton) {
let runId = arc4random_uniform(10000000)
let queue = DispatchQueue(label: "myqueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.never, target: nil)
queue.async {
sleep(4)
DispatchQueue.main.async {
self.statusLabel.text = "Status: didPressDoNotStuckIt Complted (\(runId))"
}
}
}
When I trigger didPressDoNotStuckIt, things work like what I expected. The button itself worked as normal and the animation kept going on.
The weird part is didPressStuckIt. The button got stuck and remained unresponsive since the computation was blocking the main thread, but the animation was still running smoothly.
Can anybody explain why the animation was not stuck?

Resources