Swift animateWithDuration Completion Runs When Modal View is Pushed? - ios

The following code animates one of my views and has a completion block
UIView.animateWithDuration(0.5, delay: timeToShow, usingSpringWithDamping: 0.75, initialSpringVelocity: 2, options: nil, animations: {
self.murmurComposeTextView.frame = oldFrame
self.showNewMurmurView.frame = self.getLeavingBGFrame()
}, completion: { finished in
self.pickerButtonBig.enabled = true
self.pickerButton.enabled = true
self.isShowingNewMurmur = false
self.murmurComposeTextView.becomeFirstResponder()
})
If I present a modal view while this is running, then quickly dismiss it, I notice 2 things:
The animated hasn't completed, but proceeds as usual (which I want).
The completion block has already run, as pickerButton is enabled, self.murmurComposeTextView.becomeFirstResponder has run, etc. This is what I don't want.
Does anyone know if this behaviour is on purpose, or if I have a bug, and if there's anyway to fix it? Thank you!

The "finished" Bool that is passed into the completion block indicates whether or not the animation was actually completed when the completion block is called.
So you can check that Bool to determine what you want to do. Something like:
UIView.animateWithDuration(0.5, delay: timeToShow, usingSpringWithDamping: 0.75, initialSpringVelocity: 2, options: nil, animations: {
self.murmurComposeTextView.frame = oldFrame
self.showNewMurmurView.frame = self.getLeavingBGFrame()
}, completion: { finished in
if (finished) {
self.pickerButtonBig.enabled = true
self.pickerButton.enabled = true
self.isShowingNewMurmur = false
self.murmurComposeTextView.becomeFirstResponder()
}
})
The docs:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/clm/UIView/animateWithDuration:animations:completion:

Related

UIView Animation Options Repeat count

I'm having some issue with my Swift code, I'm trying to make an UIImageView object fade away and reappear once, but having some issues with having the animation to play only once.
#IBOutlet weak var ball: UIImageView!
#IBAction func onFadeClick(_ sender: Any) {
UIView.animate(withDuration: 1, delay: 0.0, options: [.repeat, .autoreverse], animations: {
self.ball.alpha = 0.0
}, completion: nil)
}
I have attempted to read the documentation and previous questions but all have mentioned to use setAnimationRepeatCount, but xcode has an error stating that it is depreciated in iOS13 (which also does not work). Is there any built in functions I could use to stop the animation after it playing once? I have read somewhere saying using a callback function and reinitialize the animation but I am not exactly sure how to do that. Or is it a better idea to use UIViewPropertyAnimator instead of UIView.animate?
I'm just starting to learn Swift, any help or guidance is appreciated!
You can use the following to achieve your goal :
UIView.animateKeyframes(withDuration: 2, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.ball.alpha = 0.0
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.ball.alpha = 1
}
}) { (isFinished) in
}
Swift 5.x
The current iOS syntax replacement for deprecated approach could be writed like the example below:
UIView.animate(withDuration:1,
delay: 0.5,
options: [.curveEaseInOut, .autoreverse, .repeat]) {
UIView.modifyAnimations(withRepeatCount: 3, autoreverses: true) {
print("repeated stuff")
}
} completion: { _ in
print("done")
}
The iOS 13 replacement for the deprecated approach is https://developer.apple.com/documentation/uikit/uiview/3043564-modifyanimations. Example:
UIView.animate(withDuration:1, animations: {
UIView.modifyAnimations(withRepeatCount: 3, autoreverses: true, animations: {
// whatever
})
})
However, the deprecated approach does still work; it is just deprecated.
instead of the last }) use:
}, completion: { finished in
self.alpha = 1
})

Cocoa Animation not triggering when called by button

I'm developing an iOS app and i've been asked to add animations to make it more user friendly.
So i want to animate a badge on my button displaying quantity. When the quantity changes, my function valueForItemChanged is called, then i change the value in the bimButtonBadge label and i use a animation which makes its size bounce.
But I'm facing a problem: when i call .animateWithDuration() from an event triggered by a button, it doesn't work:
#IBAction func valueForItemChanged(sender: AnyObject) {
print("value changed");
self.bimButtonBadge.text = String(self.getTotalItemQuantity())
self.bimButtonBadge.transform = CGAffineTransformMakeScale(0.2, 0.2)
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 4.0,
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {
self.bimButtonBadge.transform = CGAffineTransformIdentity
}, completion: nil)
}
So i tried to make this animation in viewDidLayoutSubviews(), just to see. It worked.
I think viewDidLayoutSubview is called from the main thread so i tried this :
#IBAction func valueForItemChanged(sender: AnyObject) {
print("value changed");
self.bimButtonBadge.text = String(self.getTotalItemQuantity())
self.bimButtonBadge.transform = CGAffineTransformMakeScale(0.2, 0.2)
dispatch_async(dispatch_get_main_queue(), {
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 4.0,
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {
self.bimButtonBadge.transform = CGAffineTransformIdentity
}, completion: nil)
});
}
Aaaaaand, it worked, partially. Sometimes the label disappears. I searched on the web, but i couldn't find anything related to this. So i'm wondering where i'm wrong.
If anyone could answer me it would be really appreciated.
Thanks
Your animation (both versions) sets the badge's transform to the identity transform, but you don't show any code that sets the transform to anything other than the identity transform. An animation animates a change in a property. If you don't actually change anything, it won't animate.
What are you trying to do? You say you're using a bounce animation, but what do you mean? Bouncing the size? The position?
Problem solved.
I solved this by doing my first transformation in the main thread thanks to Duncan C.
My code is now :
#IBAction func valueForItemChanged(sender: AnyObject) {
print("value changed");
dispatch_async(dispatch_get_main_queue(), {
self.bimButtonBadge.text = String(self.getTotalItemQuantity())
self.bimButtonBadge.transform = CGAffineTransformMakeScale(0.2, 0.2)
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 4.0,
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {
self.bimButtonBadge.transform = CGAffineTransformIdentity
}, completion: nil)
});
}

Simple Animation Issue Xcode 7 Swift

I can't seem to make a simple fade-in fade-out animation in Xcode.
All I want to do is make an image fade in 2 seconds after the viewDidLoad (which works fine), but once I add code for the fade out animation the image is never initially hidden.
Here is my code:
image.alpha = 0
UIView.animateWithDuration(1, delay: 2, options: [], animations: { () -> Void in
self.image.alpha = 1
}, completion: nil)
UIView.animateWithDuration(1, delay: 3, options: [], animations: { () -> Void in
self.image.alpha = 0
}, completion: nil)
I am using UIImages for the animation.
Try this out:
UIView.animateWithDuration(1, delay: 2, options: [], animations: { () -> Void in
self.image.alpha = 1
}, completion: {
UIView.animateWithDuration(1, delay: 3, options: [], animations: { () -> Void in
self.image.alpha = 0
}, completion: nil)
})
Simple Rule: Wait for one animation to complete before you start off with other. You were executing them back to back that was resulting first one in no effect state.
Simply chaining UIView.animateWithDuration animations in the same scope like that is problematic. A way to achieve the effect you described would be to call the second animation in the completion block of the first one, like so
self.image.alpha = 0
UIView.animateWithDuration(1, delay: 2, options: [], animations: { () -> Void in
self.image.alpha = 1
}, completion: { finished in
UIView.animateWithDuration(1, delay: 0, options: [], animations: { () -> Void in
self.image.alpha = 0
}, completion: nil)
})
Edit: this doesn't directly relate to the problem you're describing, but just to add a bit of general advice based on the wording of your question: it's usually better to start initial timed animations in the viewWillAppear or viewDidAppear methods of your view controller. viewDidLoad getting called does not necessarily mean that the view is being displayed right away, which might throw your timing off (especially if your view gets more complex with regard to memory and resources).

iOS: springWithDamping like animation for KeyFrame animation

I had the following custom transition using UIView.animateWithDuration(...usingSpringWithDamping:...) which worked perfectly.
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0,
options: nil, animations: {() in
// ...
}, completion: {(Bool) in
// ...
})
But then I had to extend my custom transition with UIViewControllerInteractiveTransitioning in order to have an interactive transition where the user can swipe down the modal view again.
Therefore I needed keyframes for the animation in order that the UIPercentDrivenInteractiveTransition works properly.
So I changed the animation function to use UIView.animateWithKeyframes....
UIView.animateKeyframesWithDuration(self.transitionDuration(transitionContext),
delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeCubic,
animations: {() in
// ...
}, completion: {(Bool) in
// ...
})
My problem now: I lost the spring animation.
I've checked several links, one of the most promising was:
Stackoverflow #1
... but with .addKeyframes... method I cannot specify a completion block that I need.
Any suggestions? :-/

How to cancel previous animation when a new one is triggered?

I am writing a camera app, and have trouble with showing the focus square when user tap on the screen.
My code is (in swift):
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
However, if use tap the screen consecutively when the previous animation does not finish, the old and new animation will mix up and the focus view will behave strangely, for example it will disappear very quick.
Could anyone tell me how to cancel previous animation, especially the previous completion block?
You can user method removeAllAnimations to stop animation
Replace your code with below
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
self.focusView.layer.removeAllAnimations() // <<==== Solution
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
Reference : link
#Jageen solution is great, but I worked with UIStackView animation and there I needed additional steps. I have stackView with view1 and view2 inside, and one view should be visible and one hidden:
public func configureStackView(hideView1: Bool, hideView2: Bool) {
let oldHideView1 = view1.isHidden
let oldHideView2 = view2.isHidden
view1.layer.removeAllAnimations()
view2.layer.removeAllAnimations()
view.layer.removeAllAnimations()
stackView.layer.removeAllAnimations()
// after stopping animation the values are unpredictable, so set values to old
view1.isHidden = oldHideView1 // <- Solution is here
view2.isHidden = oldHideView2 // <- Solution is here
UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
view1.isHidden = hideView1
view2.isHidden = hideView2
stackView.layoutIfNeeded()
},
completion: nil)
}
In my case, I add the focusing indicator by using addSubview().
self.view.addSubview(square)
square is the UIView I defined in the function. So when the user tap the screen to focus, this function will add a square on subview.
And to cancel this animation when the next tap happen, I just simply use the removeFromSuperview() function, when this function called it removes the view from its superview which is the focusing square here.
filterView.subviews.forEach({ $0.removeFromSuperview() })
It's different from the method above to remove the animation, but remove the subview directly.
In my case, i just needed to set the value, i animated to concrete value.
For e.g.
if ([indexPath isEqual:self.currentPlayerOrderIndexPath])
{
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.15,1.15);
}
completion:NULL];
}
else
{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.0, 1.0);

Resources