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
})
Related
I basically have a song that a cartoon needs to dance to.
Is it better to have :
Version A: one full song and dispatch a bunch of queues:
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
//Have the figure dance move 1
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
//Have the figure dance move 2
}
Or segment the song and actions:
func dancing(){
timeElapsed += 1
if timeElapsed == 1 {
\\figure does move 1
self.playSound()
} else if timeElapsed == 2 {
\\figure does move 2
self.playSound2()
}
As a summary:
Version a: Dispatch multiple queues at the same time
Version b: Segment the queues, but that would mean my project would have 10+ media files
Is there anyway to test this? Or any alternative methods? I've poked around and seen things like concurrent/sync queues, but don't know much on how to use them in practice.
If you don’t want too much nesting, you can use UIViewPropertyAnimator. For Example:
let animation1 = UIViewPropertyAnimator(duration: 0.5, curve: .linear) {
// animation code
}
let animation2 = UIViewPropertyAnimator(duration: 1, curve: .linear) {
// animation code
}
animation1.addCompletion { _ in
animation2.startAnimation()
}
animation1.startAnimation()
Another option you might want to look at is UIView key frame animations.
You need to figure out your relative start times and durations, but it's a bit nicer than using dispatch queues.
More info: link
A simple demonstration:
UIView.animateKeyframes(withDuration: 4.0, delay: 0.0, options: [], animations: {
// Animation 1 that starts immediately and runs for 2 seconds
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5) {
// Perform animation
}
// Animation 2 that starts after 2 seconds and runs for 2 seconds
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
// Perform animation
}
})
I will go with Version A: one full song and Set the Animation Hierarchy
Better to use UIView Animation with completion block
UIView.animate(withDuration: 0.5, animations: {
//animation 1
}, completion: { (value: Bool) in
UIView.animate(withDuration: 1.0, animations: {
//animation 2
})
})
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 see code to make text in UILabel to blink, but I am using Swift 2, and what changes does one make to have such text blink in Swift?
I just need this style only to alert the user of my app to start the game, then I don't need any other text to blink.
If you have provided some code it were simpler to answer but without it it's I can do:
let foo = UITextField()
UIView.animateWithDuration(0.3, animations: {() -> Void in
foo.alpha = 0.0
},
completion: { finished in
UIView.animateWithDuration(0.3, animations: {
foo.alpha = 1.0
})
})
You can do this by using animateWithDuration.
Here i am writing in viewDidLoad(). you can use this according to your requirement.
override func viewDidLoad() {
super.viewDidLoad()
self.myLabel.alpha=0
UIView.animateWithDuration(1, delay: 0.2, options:[.Repeat,.Autoreverse],
animations:{ self.myLabel.alpha=1.0}, completion: nil)
}
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).