I am trying to move an image multiple times. This is how i tried implementing it.
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(1, animations: { () -> Void in
self.sattelite.center.x = 50
self.sattelite.center.y = 100
self.sattelite.center.x = 50
self.sattelite.center.y = 50
self.sattelite.center.x = 50
self.sattelite.center.y = 300
})
}
The animatewithduration method however only executes one of the movements. Is there any way to animate all three movements of the image view? Thanks.
you can easily chain animations ; start another at completion :
Here an illustration :
//1st animation
UIView.animateWithDuration(1.0,
delay: 0.0,
options: .CurveEaseInOut | .AllowUserInteraction,
animations: {
//some code ex : view.layer.alpha = 0.0
},
completion: { finished in
//second animation at completion
UIView.animateWithDuration(1.0
, delay: 0.0,
, options: .CurveEaseInOut | .AllowUserInteraction,
, animations: { () -> Void in
//some code ex : view.layer.alpha = 1.0
}
, completion: { finished in
//third animation at completion
UIView.animateWithDuration(1.0,
delay: 0.0,
options: .CurveEaseInOut | .AllowUserInteraction,
animations: {
//some code ex : view.layer.alpha = 0.0
},
completion: { finished in
//FINISH : 3 animations!!!
})
})
})
You need to either chain the animations with completion or use a Key Frame animation
Use the animation version which has completion:
class func animateWithDuration(_ duration: NSTimeInterval,
animations animations: () -> Void,
completion completion: ((Bool) -> Void)?)
Do the first animation only on animations and start the second in the completion. The third goes in the completion of the second.
So basically a chain of animations, where each starts when the previous finishes.
Related
Background
Thanks to the excellent answer here, I've managed to make a series of labels fade in sequentially.
However, the delay referred to in the function in the extension only triggers when the previous fade has completed (I think) so there is at least 1 second between them. Ideally I want to edit the model so that the delay refers to from the time the view loads, not the time the previous one has completed.
i.e. If I make the delay quite small, the second one would start fading in before the first one had finished.
This is the extension :
extension UIView {
func fadeIn(duration: TimeInterval = 1.0, delay: TimeInterval = 0.0, completion: ((Bool)->())? = nil) {
self.alpha = 0.0
UIView.animate(withDuration: duration, delay: delay, options: .curveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion)
}
}
and this is my implementation on a VC with 6 labels :
func animateLabels() {
self.titleOutlet.alpha = 0.0
self.line1Outlet.alpha = 0.0
self.line2Outlet.alpha = 0.0
self.line3Outlet.alpha = 0.0
self.line4Outlet.alpha = 0.0
self.line5Outlet.alpha = 0.0
self.line6Outlet.alpha = 0.0
self.titleOutlet.fadeIn(delay: 0.2, completion: { _ in
self.line1Outlet.fadeIn(delay: 0.4, completion: { _ in
self.line2Outlet.fadeIn(delay: 0.6, completion: { _ in
self.line3Outlet.fadeIn(delay: 0.8, completion: { _ in
self.line4Outlet.fadeIn(delay: 1.0, completion: { _ in
self.line5Outlet.fadeIn(delay: 1.2, completion: { _ in
self.line6Outlet.fadeIn(delay: 1.4, completion: { _ in
})})})})})})})
}
What am I trying to get to?
So what I'm trying to get to is having line2outlet start to fade 0.4 seconds after the load of the page but it is starting only when line1 has finished fading in (which takes 1 second and is delayed by 0.2s).
What have I tried?
I have attempted to write my own delay function after reading up. I tried adding this to the extension :
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
closure()
}
}
and implementing like this :
self.line1Outlet.fadeIn(delay: 0, completion: { _ in
delay(interval: 1, closure: ()->Void)
but that got loads of errors! I also tried setting minus numbers for the delays but that didn't work either.
Any help much appreciated!
One possible solution that I have used before is to use a loop with an incrementing delay variable feeding into the standard UIView.animate() method. This method does not use completion handlers.
Make an array of the labels:
var labels = [titleOutlet, line1Outlet] // etc...
Use this extension:
extension UIView {
func fadeIn(duration: TimeInterval = 1, delay: TimeInterval = 0) {
self.alpha = 0
UIView.animate(withDuration: duration, delay: delay, options: [], animations: { self.alpha = 1 }, completion: nil)
}
}
Then use it like this:
var delay = 0.2
var duration = 0.5
for label in labels {
label.fadeIn(duration: duration, delay: delay)
delay += 0.2 // Increment delay
}
I have a CollectionView and I want to create an animation inside the CollectionViewCell selected by the user. I chose to use animateKeyframesWithDuration because I want to create a custom animation step by step. My code looks like this:
func animate() {
UIView.animateKeyframesWithDuration(1.0, delay: 0.0, options: .AllowUserInteraction, animations: { () -> Void in
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.5, animations: { () -> Void in
// First step
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: { () -> Void in
// Second step
})
}) { (finished: Bool) -> Void in
if self.shouldStopAnimating {
self.loadingView.layer.removeAllAnimations()
} else {
self.animate()
}
}
}
This is executed inside the custom CollectionViewCell when it is selected.
The problem is that I want to force stop the animation immediately at some certain point. But when I do that, the animation doesn't fully stop, it just moves the remaining animation on a different cell (probably the last reused cell?)
I can't understand why this is happening. I have tried different approaches but none of them successfully stop the animation before normally entering the completion block
Does anyone have any idea about this?
Instead of removing the animations from the layer you could try adding another animation with a very short duration that sets the view properties that you want to stop animating.
Something like this:
if self.shouldStopAnimating {
UIView.animate(withDuration: 0.01, delay: 0.0, options: UIView.AnimationOptions.beginFromCurrentState, animations: { () -> Void in
//set any relevant properties on self.loadingView or anything else you're animating
//you can either set them to the final animation values
//or set them as they currently are to cancel the animation
}) { (completed) -> Void in
}
}
This answer may also be helpful.
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).
I have an Array words containing ["alice", "bob", "charles"] and a UILabel label. I want label to repeatedly fade in and out, with a different word from words each time. If I put the text-changing code inside the animations block, it doesn’t execute, even though the fading works as expected (the completion block is run only when something stops the repetition):
label.alpha = 0
UIView.animateWithDuration(4, delay: 0, options: .Autoreverse | .Repeat, animations: {
self.label.text = nextWord()
self.label.alpha = 1
}, completion: {_ in
NSLog("Completed animation")
})
What’s a good way to fix this? Thanks.
surely not the most elegant but working solution:
#IBOutlet weak var label: UILabel!
let words = ["Is", "this", "what", "you", "want?"]
var currentIndex = -1
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
showNextWord()
}
func showNextWord() {
if currentIndex == words.count - 1 {
currentIndex = -1
}
UIView.animateWithDuration(1, delay: 1, options: UIViewAnimationOptions(0), animations: { () -> Void in
self.label.alpha = 0.0
}) { (_) -> Void in
self.label.text = self.words[++self.currentIndex]
UIView.animateWithDuration(1, animations: { () -> Void in
self.label.alpha = 1.0
}, completion: { (_) -> Void in
self.showNextWord()
})
}
}
You could construct this as a keyframe animation. That way, you can chain three animations together (one for each word) and repeat that entire chain.
Alternatively (this is what I would probably do), put one animation into a method of its own, and in the completion block, add a short delay and call the method - thus creating a perpetual loop. The loop creates the repetition, but each animation is just one animation, so now you can progress through the array on successive calls. So the structure (pseudo-code) would look like this:
func animate() {
let opts = UIViewAnimationOptions.Autoreverse
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
// animate one fade in and out
}, completion: {
_ in
delay(0.1) {
// set the next text
self.animate() // recurse after delay
}
})
}
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);