Animation completion block goes haywire after dismissing view - ios

I have a detail viewcontroller that contains a repeating carousel with an animating image view like so:
func animateCarousel1(){
UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations:
//image animation
},completion: { (_) -> Void in
print("animation 1 complete")
self.animateCarousel2()
})
}
func animateCarousel2(){
UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations:
//image animation
},completion: { (_) -> Void in
print("animation 2 complete")
self.animateCarousel1()
})
}
Upon popping this view and returning to the parent view, I see that in the debug console, the functions are continuing to be called in the background, repeatedly and simultaneously, infinitely.
CPU usage also jumps to 90% in the simulator as well.
Is there some sort of deinit I need to do before popping the viewcontroller? I can't seem to wrap my head around this one.

The issue is that completion blocks are called even if the animation finishes. So there are a few possible solutions:
Check the finished parameter of the completion handlers:
func animateCarousel1() {
UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: {
//image animation
}, completion: { finished in
print("animation 1 complete")
if finished { self.animateCarousel2() }
})
}
func animateCarousel2() {
UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: {
//image animation
}, completion: { finished in
print("animation 2 complete")
if finished { self.animateCarousel1() }
})
}
Use a different animation technique that doesn't require the circular references between these animation routines, e.g.:
func animateCarousel() {
UIView.animateKeyframes(withDuration: 8, delay: 3, options: .repeat, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.125) {
// animation 1
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.125) {
// animation 2
}
}, completion: nil)
}

It's possible this solve the problem you can see this post link here
Use on deinit() controller method or viewDidDissapear(), try the following code
deinit {
print("deinit \(NSStringFromClass(self.classForCoder).components(separatedBy: ".").last ?? "")") // print viewcontroller deallocated
self.view_for_remove_animations.layer.removeAllAnimations() // Forze delete animations
}
OR
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("viewDidDisappear \(NSStringFromClass(self.classForCoder).components(separatedBy: ".").last ?? "")") // print viewcontroller deallocated
self.view_for_remove_animations.layer.removeAllAnimations() // Forze delete animations
}
Try it, and send feedback

The reason is in animation block, you are using strong reference of self which is why when you are poping this viewcontroller its reference count is still not 0, because of which ARC is not able to dealloc the reference of this view controller.
There is a concept of [weak self]. You can modify your code like below and then after poping you won't see those method calls in your debug console. Reason: weak self will not increase the reference count and ARC will be able to remove the reference of the object
func startAnimation() {
UIView.animate(withDuration: 0.4, animations: {[weak self] in
self?.animateView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
print("startAnimation")
}, completion: {[weak self]
finished in
self?.startAnimation2()
})
}
func startAnimation2() {
UIView.animate(withDuration: 0.4, animations: {[weak self] in
self?.animateView.frame = CGRect(x: 100, y: 0, width: 100, height: 100)
print("startAnimation2")
}, completion: {[weak self]
finished in
self?.startAnimation()
})
}

Related

UserInteraction is disabled on animating view

I have button on view, while the view is animating the button is disabled for some reason
Here's the code below , I have allowUserInteraction in options but it doesn't do anything
UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction) {
view.frame.origin.y = 36
} completion: { (_) in
UIView.animate(withDuration: 0.5, delay: 2, options: .allowUserInteraction) {
view.frame.origin.y = -100
} completion: { (_) in
print("completed")
}
}
place the button outside of the view you are animating.

Animation not stoping using UIView.animate

My code is using a simple loop animation that is not stopping after the 5 second duration. All I want to do is have a second button that stops the animation. As you can see in my code, circle.stopAnimating() has no effect.
class VET: UIViewController {
#IBOutlet weak var circle: UIImageView!
var duration = 5
#IBAction func start(_ sender: Any) {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
print("animation")
}, completion: { _ in
//if finished { or (finished: Bool)
// if isAnimating {
if self.circle.isAnimating {
self.circle.stopAnimating()
print("is animating -- stop animating")
} else {
self.circle.startAnimating()
print("start animating")
}
})
}
#IBAction func stop() {
circle.stopAnimating()
}
startAnimating and stopAnimating for change images of imageview in some interval like GIF. Here you used animation block with repeat and the completion block will only get called when the animation is interrupted. For example it gets called when the app goes in the background and comes back to the foreground again.
To stop animation after some interval you have to call forcefully removeAllAnimations after that interval. Please refer following code:
#IBOutlet weak var circle: UIImageView!
var duration = 10
func start() {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.duration * 1000)) {
// Code
self.circle.layer.removeAllAnimations()
}
print("animation")
}, completion: { _ in
let scale = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.circle.transform = scale
})
}
You are using wrong approach to use animations if you need more control over it.
Instead use UIViewPropertyAnimator
The alternate property animator initializer gives you back an animator object that you need to start:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation()
Similarly you can use for stopping animations related to the animator
animator.stopAnimation()

How can I Use Alpha Extension For All Object in swift?

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

How to do a sequence animation on a textview (swift3)

My code below is a button that when hit applies animation. Right now there are two animations. I would like to do a sequence the animations meaning have the 2nd animation not start until the first animation has completed.
var speed: CGFloat = 5.3 // speed in seconds
#IBAction func press(_ sender: Any) {
self.theTextView.resignFirstResponder()
UIView.animate(withDuration: TimeInterval(speed), animations: {
////1st action[
self.theTextView.contentOffset = .zero
self.theTextView.setContentOffset(.zero, animated: true)]
/////2nd action[
self.theTextView.contentOffset = CGPoint(x: 0, y: self.theTextView.contentSize.height)]
}, completion: nil)
}}
The easiest way of doing that would be using the completion block of the animate(withDuration:animations:completion:) method. The completion block is executed when the animation sequence ends. In your case, it would look like this :
UIView.animate(withDuration: 6.0, animations: {
// First animation goes here
self.theTextView.contentOffset = CGPoint.zero
}, completion: { (finished) in
// This completion block is called when the first animation is done.
// Make sure the animation was not interrupted/cancelled :
guard finished else {
return
}
UIView.animate(withDuration: 1.0, animations: {
// And the second animation goes here
self.theTextView.contentOffset = CGPoint(x: 0, y: self.theTextView.contentSize.height)
})
})
There is also convenient way to make sequence of concurrent animations by using animateKeyframes:
UIView.animateKeyframes(withDuration: 1, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.3, animations: {
self.someView.alpha = 0.5
self.view.layoutIfNeeded()
})
//second animation
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.3, animations: {
self.someView.alpha = 1
self.view.layoutIfNeeded()
})
}, completion: { (finish) in
// Do something on animation complete
})

UIButton animation extension

I'm attempting to reduce redundancy of repetitive code that's used throughout an app by creating a standard animation function.
My desired outcome is an animation for a button with an optional completion handler. Here's my code:
extension UIButton {
func animate(duration: TimeInterval, completion: ((Bool) -> Swift.Void)?) {
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 6.0, options: [], animations: {
self.transform = .identity
}, completion: completion)
}
}
On MyViewController, when I call the animation method like this, the segue happens, but the animation does not:
myButton.animate(duration: 0.25) { _ in
self.performSegue(withIdentifier: "mySequeIdentifier", sender: sender)
}
I commented out the self.performSegue to ensure the segue was not hard-wired on the storyboard, and verified it is not.
I also tried declaring the animation function with this code:
func animate(duration: TimeInterval, completion: #escaping (Bool)->Void?) {
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 6.0, options: .allowUserInteraction, animations: {
self.transform = .identity
}, completion: completion as? (Bool) -> Void)
}
Using that code, nothing happens. Not even a crash.
Thank you for reading. I welcome your suggestions re: where my mistake lies.
Well its not animating because I think you forgot to give initial transform value to button before calling extension method.
myButton.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
myButton.animate(duration: 0.25) { _ in
self.performSegue(withIdentifier: "mySequeIdentifier", sender: sender)
}

Resources