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? :-/
Related
I have simultaneous animations going on, and I would like to transition the VC in the middle (it will fade so it will see some of the other animations). However, I can't find documentation how to delay a transition similar to UIView.animateWithDuration.
I want to achieve this...
UIView.transition(withDuration: 0.5, delay: 0.1, options: .transitionCrossDissolve, animations: {})
I can manually add a delay like so.. but was wondering if there's a more elegant way.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {}
Unfortunately no. Using a bunch of UIView transitions/animations can get a little hairy.
In the past, I've resorted to setting up an NSTimer that fires 1/30 seconds and I ended up managing all the start times on my own.
What you can do is series out the animations using the closure.
UIView.animate(withDuration: 1, animations: {
}, completion: {Do UIView transition here})
It seems no way except surrounding with Timer:
Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { _ in
UIView.transition(with: self.myImageView, duration: 0.75,
options: [.transitionCrossDissolve],
animations: {
self.myImageView.image = newImage
})
})
I am trying to have my animation ease the screen from black to white to black again and repeat that a certain amount of times. Currently with the code I have the animation eases from black to white then jumps back to black. Is there anyway to run an animation in reverse or add an animation that runs after the first animation is completed?
override func viewDidAppear(_ animated: Bool) {
let viewColorAnimator: UIViewPropertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut],
animations: {
UIView.setAnimationRepeatCount(3);
self.lightView.backgroundColor = .white
})
viewColorAnimator.startAnimation()
}
I tried adding this block of code to the project but the outcome was the same:
viewColorAnimator.addCompletion {_ in
let secondAnimator = UIViewPropertyAnimator(duration: 4.0, curve: .linear) {
self.lightView.backgroundColor = .black
}
secondAnimator.startAnimation()
}
EDIT: I've found out it is because of the setAnimationRepeatCount because the last of the iterations works properly. How do I run the animation multiple times without the repeat count?
Documentation says that the options related to direction are ignored. you can check the image attached here.
To achieve reverse animation:
Create an animator property.
private var animator: UIViewPropertyAnimator = {
let propertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut, .autoreverse],
animations: {
UIView.setAnimationRepeatCount(3)
UIView.setAnimationRepeatAutoreverses(true)
self.lightView.backgroundColor = .white
})
return propertyAnimator
}()
P.S. we need to mention the .autoreverse in the options. Otherwise UIView.setAnimationRepeatAutoreverses(true) won't work.
There's an easy way to run animations. And for this method: if you want the animation to repeat after completing, you can add the .autoreverse, and the .repeat option. Like this:
UIView.animate(withDuration: TimeInterval(randomTime), delay: 0, options: [.repeat,.autoreverse], animations: {
//Animation code here
}, completion: {(bool)
//Do something after completion
})
You can put the codes you want to execute after the animation in the completion block.
And, you can use a Timer as a way to run the animation for certain times.
Xcode 9.4.1 (Swift 4.1.2) and Xcode 10 beta 3 (Swift 4.2):
Here's the way to do it using UIViewPropertyAnimator -- basically, we just add .repeat and .autoreverse to the options. You were 99% of the way there:
override func viewDidAppear(_ animated: Bool) {
let viewColorAnimator: UIViewPropertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut, .repeat, .autoreverse],
animations: {
UIView.setAnimationRepeatCount(3)
self.lightView.backgroundColor = .white
})
viewColorAnimator.startAnimation()
}
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)
});
}
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).
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: