Swift UIView Animation block increment error - ios

I have a UIView Animation block that I want to run on paragraphs in an array.
UIView.animateWithDuration(0.4, delay: 0.2, options: .CurveEaseOut, animations: {
var frame = self.textViewForPlayer.frame
frame.origin.y += 20
self.textViewForPlayer.frame = frame
}, completion: { finished in
UIView.animateWithDuration(0.4, delay: 0, options: .CurveEaseOut, animations: {
var frame = self.textViewForPlayer.frame
frame.origin.y -= 80
self.textViewForPlayer.frame = frame
}, completion: { finished in
})
}
)
textViewForPlayer = paragraphs[++currentParagraph]
Strange thing is - the first block (first animation before completion) runs the animation on the first paragraph, but the chained animation in the first completion block runs on the second paragraph (somehow the "paragraphs[++currentParagraph]" is executing in between the two blocks of animation.
Not sure how this is happening, the increment code is after the animation itself, so it seems bizarre or maybe even a bug that causes it to execute in the middle.

Your problem is that the animations and completion block are called at a latter time, but animateWithDuration returns immediately.
Everything outside the scope of the blocks will run without respect to the delay of the animation or the animation being finished.
Consider putting the incrementation inside your last finished block. This should increment the number at the end of both animations.
...,completion: { finished in
textViewForPlayer = paragraphs[++currentParagraph]
})

Related

UIViewPropertyAnimator with short animation segments in the beginning

Imagine UIViewPropertyAnimator is setup as follows:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
myView.center = newCenterPoint
}
For this, I can add another animation that starts with a delay, ends with the main:
animator.addAnimation ({
myView.alpha = 0.5
}, withDelayFactor: 0.5)
So above starts at 1 second and continues for 1 second to finish with the main.
Question
Is there a way to add an animation to UIViewPropertyAnimator that starts at the beginning, but continues for a fraction of the time of the main animator? Starts at 0 and continues only for 1 second?
Why not just use another animator? It's fine for two animators to act on the same view simultaneously as long as their animations don't conflict:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
self.myView.center.y += 100
}
let animator2 = UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
self.myView.backgroundColor = .red
}
animator.startAnimation(); animator2.startAnimation()
The animation moves the view downward for two seconds, while turning the view red during the first one second.
(Having said all that, in real life I'd probably use a CAAnimationGroup for this, rather than a property animator.)

Custom animation: easing out

I have successfully created a custom animation in a drawing in a UIView by using a repeating Timer to increment the alpha of the drawing every few milliseconds.
Now, I want to achieve an easing out animation (deceleration) with my drawing. I would like to do this by firing a new timer with a longer interval every time the Timer is called, so that the alpha increments slower, resulting in deceleration.
I know that there is an easeOut animation from CAMediaTiming, but I would like to know if there is any built in function to get the decelerating numbers. For example, if I pass in a constant of 10, every time I call the function I can get decelerating numbers like 15, 18, 20, 21, 21.5, etc.
There is a built-in way to use various sorts of animation curves in standard UIView animations.
var yourView = UIView() // Or whatever your view might be
var yourView.alpha = 0 // Initial alpha value
// Call the animation method. Note options (which can be an array) which applies an ease-out curve to the animation
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseOut, animations: {
yourView.alpha = 1.0 // Final value of alpha
}, completion: nil)
Other animation curves are available, such as .curveEaseIn and .curveEaseInOut as well as other options. You can read more about animation options here.
You can use the completion handler closure to chain animations too.
If you are insisting on implementing your own means of animation using a timer (which I would not recommend), post some code so that I can consider a way to apply a curve to the changing values.

iOS Animation Repeat (Completion Block Not Being Called)

I'm trying to get my animation to move back and forth. For example, in a fish tank simulation as such, I am trying to get the fish to move back and forth. I am trying to get this to work infinitely long, however the fish only moves in one direction and the completion block is never being called. Can someone advise me how to call the completion block once the fish reaches the end of the screen or another way to approach this?
Attached is my current code:
UIView.animate(withDuration: fishAnimationDuration, delay: fishAnimationDelay, options: [.allowAnimatedContent, .repeat], animations: {
fish.frame = CGRect(x: fishXPosEnd, y: fishYPos, width: fishWidth, height: fishHeight)
}, completion: { (finished: Bool) in
fish.image = #imageLiteral(resourceName: "fishBack")
UIView.animate(withDuration: fishAnimationDuration, delay: fishAnimationDelay, options: [.allowAnimatedContent], animations: {
fish.frame = CGRect(x: fishXPos, y: fishYPos, width: fishWidth, height: fishHeight)
})
})
The completion block isn't called when repeat is in the options because the repeating animation never completes.
In your case I would remove the repeat option. Then in the completion handler of the 2nd animation, call the method containing these animation blocks so the pair of animations are called again.

iOS animation malfunctioning, Y-Coordinate Problems

For hours I've been trying to solve the smallest animation glitch. My code successfully moves the view off screen, then animates it back in. It gets the x-coordinations right but the Y axis has behavior I don't understand. Here's the code:
func listTrans_slideIn (slideFrom: String) {
//var newFrame = tableView.frame
tableView.frame.origin.x = 1000
//tableView.frame.origin.y = 100
print("Table pushed to side")
UIView.animateKeyframesWithDuration(1.375 /*Total*/, delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 1/1, animations:{
self.tableView_toTLG_Top.constant = 130
self.tableView_toSV_Left.constant = 0
self.tableView_toSV_Right.constant = 0
self.setupView()
//newFrame.origin.y = self.hdrBox.frame.height+50
//newFrame.origin.x = 0
//self.tableView.frame = newFrame
self.tableView.layoutIfNeeded()
})
},
completion: { finished in
if (!finished) { return }
})
}
The weird behavior is that if I put the correct y-coordinate in the animation keyframe, it comes in too high but then settles at the correct coordinate. If I put in a y-Coordinate that is too low, it comes in at the correct height but then settles too low.
As you can see, I've tried using frames and constraints. I've tried changing the height that I move it off screen to, but that seems to have no effect.
Anyone have any idea why I've spent half my day seeing this bug?
Can you try something like this :
// Use the same value here and below, it moves it out of the screen
self.tableView.center.y -= 500
UIView.animateWithDuration(1.0, animations: { () -> Void in
// Then it comes back
self.tableView.center.y += 500
})
This turned out to be an order of operations problem. The solution required that I move the animation to take place in the viewDidAppear.
Other notes:
To make the animation look as smooth as possible, consider turning off the segue's animation.
Also make sure you're running the segue call and animation in your main thread so that everything happens smoothly and without delay

Delay after each animation iteration in Swift

I have a Core Animation whose .repeatCount is set to Float.infinity. After each iteration of the Animation, ie. after each repetition, I want to have a delay of 3 seconds. How can I achieve this? Thanks!
You can use a function like the following to do what you need.
func animateInfinitelyWithDelay(delay: TimeInterval, duration: TimeInterval) {
UIView.animate(
withDuration: duration,
delay: delay,
options: UIView.AnimationOptions.curveEaseIn,
animations: { () -> Void in
// Your animation Code
}) { (finished) -> Void in
if finished {
self.animateInfinitelyWithDelay(delay: delay, duration: duration)
}
}
}
One way to accomplish this effect using Core Animation is to configure everything except the repeat count on the original animation object and then wrap it in an animation group with a longer duration and repeat that animation group instead.
let originalAnimation = /* create and configure original animation ... */
originalAnimation.duration = shortDuration
let group = CAAnimationGroup()
group.animations = [originalAnimation]
group.duration = shortDuration + delayAtTheEnd
group.repeatCount = .infinity
theLayer.add(group, forKey: "repeating animation with delay between iterations")
Note that depending on what the original animation is doing, you may need to configure a fill mode to achieve the right look.
You could also use a UIView keyframe animation (animateKeyframesWithDuration) where there is "dead time" built into the animation at the end, and then repeat that animation.

Resources