animateWithDuration Animates Once But Not Twice - swift

I have this piece of animation in a switch statement that runs and does what it's supposed to. The animation is called successfully every twenty seconds inside a timer. However, the animation does not happen any time but the first time. In my code below you will see println statements that I used to try and highlight the problem. Each println statement prints but no animation happens. What is it that I'm missing?
func popAnimation(indexNumber: Int) {
let image = self.overlayImageView[indexNumber]
image.hidden = false
image.alpha = 0.0
UIView.animateWithDuration(0.75, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: nil, animations: { () -> Void in
println("animation ran")
println(image.alpha)
image.image = UIImage(named: "lowCountOverlay")
image.alpha = 1.0
}, completion: { (Bool) -> Void in
UIView.animateWithDuration(0.75, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: nil, animations: { () -> Void in
println("animation 2 ran")
println(image.alpha)
image.alpha = 0.0
}, completion: { (Bool) -> Void in
println("animation 3 ran")
image.hidden = true
})
})
}
UPDATE: If I remove image.hidden from the second completion block it works fine and animates repeatedly. Which is interesting because if it's not hidden it should be covering other interactive content in the view, but it's not. The other content is perfectly accessible in simulator. The UIImageView image is definitely the top layer in Storyboard.

I could be off, but it seems like you're running from an NSTimer which may or may not be in the main thread.
UI updates need to take place in the main thread, try wrapping the completion handler in a GCD dispatch directed to the main thread like the following and see if that helps? Maybe even force the whole thing to the main thread.
UIView.animateWithDuration(0.75, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: nil, animations: { () -> Void in
// First animation
}, , completion: { (Bool) -> Void in
dispatch_async(dispatch_get_main_queue(),{
// second animation
})
})

I think you need to look for your bug elsewhere. You don't seem to be missing anything in the codeā€”as I'm able to run the animation repeatedly without any issues. I've copied your code as-is to a separate project and the animation works every time.

Try replacing:
let image = self.overlayImageView[indexNumber]
image.hidden = false
image.alpha = 0.0
with
let image = self.overlayImageView[indexNumber]
image.superview.bringSubviewToFront(image)
image.hidden = false
image.alpha = 0.0

Related

Why is my button being removed from its superview before it completes its animation?

I am trying to move an my button to a certain part of the screen [as a custom button class GameBlock] and then remove it from its superview. However, it is acting weird in the sense that it removes itself from the superview before it completes the animation and I have no clue why. May somebody please help me out? I appreciate it.
UIView.animate(withDuration: movementTime,
delay: 0.0,
options: [],
animations: {
movedBlock.center = gridCoord[hitBlock.x][hitBlock.y]
},
completion: nil)
if movedBlock.center == gridCoord[hitBlock.x][hitBlock.y] {
movedBlock.removeFromSuperview()
}
If I understand your problem, this should works. Your view will be removed after the animation. The completion closure will be called at the end of the animation.
UIView.animate(withDuration: movementTime,
delay: 0.0,
options: [],
animations: {
movedBlock.center = gridCoord[hitBlock.x][hitBlock.y]
},
completion: { _ in
if movedBlock.center == gridCoord[hitBlock.x][hitBlock.y] {
movedBlock.removeFromSuperview()
}
})

Delay in DispatchQueue.main.async

I have a design like below flow. I need to set delay between 5th and 6th steps for 0.3second. I tried below options but couldn't get any result.
My question is, how can I achieve this?
Note: 13seconds for see the animation.
Flow
Task Handler // for webService request
Closure Handler // for trigger ViewController
DispatchQueue.main.async // for Update UI
First Animation
Second Animation
Navigation to next screen
Test 1
Timer.scheduledTimer(withTimeInterval: 13, repeats: false, block: {})
Test 2
UIView.animate(withDuration: 13, animations: {
// nothing should be happened
self.ivSuccessMark.alpha = 0.99 // for dummy animation diff
}, completion: { (completion) in
// navigation
})
Test 3
perform(_:with:afterDelay:)
Try this one
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
// animation stuff
}, completion: { _ in
// do stuff once animation is complete
})
Try this one I hope it help you(Up to 4 seconds Stop all Action in view)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
// Put your code which should be executed with a delay here
})

Swift Animate duration not working in CGAffineTransform

When I am translating one view with an animation of 1 second it is not working, but when I am executing the transform.identity it works fine.
Here is my code:
func hideCarousel() {
UIView.animate(withDuration: 1, animations: {
self.carouselER.transform = CGAffineTransform(translationX: 0, y: 200)
})
}
func showCarousel() {
UIView.animate(withDuration: 1, animations: {
self.carouselER.transform = .identity
})
}
To solve this issue I forced the animation to run in the main thread. Every time that you have problems with the performance of your UI elements like your animations or updating your label texts, try forcing to run the UI change in the main thread.
DispatchQueue.main.async {
UIView.animate(withDuration: 1, animations: {
self.carouselER.transform = CGAffineTransform(translationX: 0, y: 200)
})
}
I have also faced that problem with one timer that updated a label, but in this issue I thought it was some kind of problem of the CGAffineTransform.

Changing UIButton alpha then doesn't recognize tap

I set up an interface very much like youtube in the sense that if a user presses on a UIView, I'll hide or show all of the subviews with an animation.
func setupControlViewVisiblity(playingVideo: Bool = false, duration: TimeInterval = 1.0, isExpanded: Bool = false){
var alpha:CGFloat = 1
if isShowingControls {
//If we're showing then we're about to hide everything so alpha is 0
alpha = 0
}
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.pausePlayButton.alpha = alpha
self.backButton.alpha = alpha
self.infoLabel.alpha = alpha
self.fullScreenButton.alpha = alpha
self.currentTimeLabel.alpha = alpha
self.videoLengthLabel.alpha = alpha
self.videoSlider.alpha = alpha
}, completion:nil)
isShowingControls = !isShowingControls
}
The issue that I'm having is that if I call this method and I show the controls (isShowingControls is true), my UIButtons will not recognize the first tap. This literally only happens when the views alpha go from 0 to 1 then I try to tap a button.
I tried doing the animation on the main queue... doesn't work. If I change the alpha from 0 to 1.... wait a few seconds... then tap the button.. it works. How can I make it work right after the UIButtons alpha changed?
Thanks
EDIT: I guess it has to do with the animation duration because when I switch it down to 0.2 it works. How can I keep it a bit higher and get this to work?
By default UIVIew animations do not allow user interaction while the animation is animating.
Luckily there is an animation option allowUserInteraction. Not much more information but you can see it in documentation here. Also worth checking out all the options here.
Try this:
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.curveEaseOut, .allowUserInteraction], animations: {
// Update the views
}, completion:nil)

Overlapping UIView animation canceling the first completion block

I have two buttons to move views. When I press the first button it will move a view and the other will move it back.
The first animation has a completion block, but I want to prevent this block from executing when the user moves the view back before the completion is done.
I have tried using solutions given to other questions similar to this but it doesn't seem to stop the execution of the first completion block.
#IBOutlet func moveView(sender: AnyObject) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .AllowUserInteraction, animations: { /* animation */ }) { _ in /* completion */ }
}
#IBOutlet func moveBack(sender: AnyObject) {
UIView.animateWithDuration(0.0, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .BeginFromCurrentState, animations: { /* animation */ }) { _ in /* completion */ }
}
I also tried removing all the animations and the completion parameter will be false but the animation will just jump to the end and animate from there.
Any ideas is very much appreciated.
You may use boolean flag to achieve the desired functionality like as under:
var moveViewFlag = false
var moveBackFlag = false
#IBOutlet func moveView(sender: AnyObject) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .AllowUserInteraction, animations: { /* animation */ }) { _ in /* completion */
//inside completion block make the moveBackFlag to true
moveBackFlag = true
}
}
#IBOutlet func moveBack(sender: AnyObject) {
UIView.animateWithDuration(0.0, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .BeginFromCurrentState, animations: { /* animation */ }) { _ in /* completion */
//inside completion block make the moveBackFlag to true
moveViewFlag = true
if moveBackFlag{
//do whatever you want to do!
}
}
}
Hope this helps.
Than you should create a custom class and handle that inside the class to avoid cluttered flags.
After searching for a while I think I've found a solution. To get the completion block to return false, the animation has to be removed. But by doing so, the animation will jump to the "end state" so to prevent this I used:
object.layer.frame = object.layer.presentationLayer()!.frame
object.layer.removeAllAnimations()
From here I can start my new animation (moving back the object) without worrying about the completion block of the first animation. Information
I'm not sure if this is how it should be done or if there's a better way of doing this.