iOS Animation Repeat (Completion Block Not Being Called) - ios

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.

Related

Creating an MKMapKit curved / spherical / interpolated camera transition

I would like to animate the camera so that the entire animation feels "curved" rather than "linear."
Right now, with the out-of-the-box camera animation iOS offers, or even when using UIView.animate(), the animation feels like the camera is directly moving from the start to the end. However, I would like the animation to feel like it is curving from the start to the end.
Here's a picture describing what it feels like now vs what I would like it to feel like.
Here is an example from Kakao maps, a Korean app, of what the desired animation would be:
Currently, I'm just using two camera animations, to create a flying-out and flying-in effect. When I create my own custom animations for MKMapView's camera with a curveEaseInOut, the speed of the animation is curvedEaseInOut, but this does not create the desired effect. This creates the undesirable "linear" feeling as depicted in the "What it's now:" diagram above
UIView.animate(withDuration: duration*2,
delay: 0,
options: .curveEaseIn,
animations: {
self.mapView.camera = preRotationCamera
}) { _ in
UIView.animate(withDuration: duration*2,
delay: 0.1,
options: .curveEaseOut,
animations: {
self.mapView.camera = finalCamera
}, completion: completion)
}
I image that using animateKeyframes(withDuration:delay:options:animations:completion:) could be a valid approach. However, I have not been able to successfully animate the MKMapView camera through a series of coordinates with animateKeyframes() — the keyframes seemed to be ignored, and the camera just directly transitions to the final camera.
For example, this code simply transitions the camera directly to finalCamera:
UIView.animateKeyframes(withDuration: duration*2, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5) {
self.mapView.camera = preRotationCamera
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.mapView.camera = finalCamera
}
})
How could one create this kind of spherical / interpolated / curved camera transition for iOS?
For reference, this is with iOS15 and Swift 5.

How can I set the old transformation after the animation has finished?

I have a scene with several triangles on it with different colors and so on which the users can create when they touch the view. The triangle is placed where the user touched the view and is added to a topView (which is linked to a context menu but this is something different).
I want to apply the current transformation of my triangle after the animation has finished.
Right now it animates it 4 times and when the animation has finished it shrinks abrupt without an animation and places it not at the old position.
let invertedTransform = topView.transform.inverted()
let triangles = TriangleView()
triangles.transform = invertedTransform
topView.addsubview(triangles)
triangleArray.append(triangles)
for triangle in triangleArray {
UIView.animate(withDuration: 1, delay: 0, options: [.repeat,.autoreverse], animations: {
UIView.setAnimationRepeatCount(4)
triangle.transform = CGAffineTransform(scaleX: 2.4, y: 2.4) }, completion: { done in
if done {
triangle.transform = CGAffineTransform(scaleX: 1, y: 1)
}
})
}
I think the mistake is in the completion part.The .autoreverse option is doing the reverse animation fine but when it finishes it doesn't shrink back.
How can I save my the current transformation of my triangle and set a pulsating animation for it?
UPDATE
I forgot to mention that the every triangle has a rotation value which is stored in a database:
triangle.rotation.value
The default transformation which is indeed given through the .identity method is the rotation value 0. When a rotation is applied on a triangle object it should set it back to the original value which is stored in the triangle object above:
let triangles = TriangleView(x: touchpoint.x, y: touchpoint.y, rotation: triangle.rotation.value)
The completion part should probably be (not enough code provided to test it):
if done {
triangle.transform = CGAffineTransform.identity // or just .identity
}
That sets the transform state back to "original" / "no transform".
Edit
If your object already has a transform, you'll need to either save it or re-create it.
So, for example, add a property to your TriangleView that saves its "initial" state:
var initialTransform: CGAffineTransform!
When you init the view and setup the rotation transform, save that:
self.initialTransform = self.transform
Your animation completion then becomes:
if done {
triangle.transform = triangle.initialTransform
}

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.

How to make a visual bounce circle for record voice

I want to make a UIView, circle, that scale based on volume of speak recording. I have this code to obtain the rate:
recorder.updateMeters()
let ALPHA = 0.05
let peakPower = pow(10, (ALPHA * Double(recorder.peakPower(forChannel: 0))))
How can I make this animation? I I make only:
self.audioCircle.transform = CGAffineTransform(scaleX: 1+CGFloat(rate), y: 1+CGFloat(rate))
the animation is too static, I need a more natural bounce effect, how can I do?
thanks!
.transform is what's implicitly animating your views change in size.
In that case, you should try using:
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.75, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
//update view
self.view.layoutIfNeeded()
}, completion: { (completed) in
//animation completed
})
The above animation might be what you want to use to animate your view. It gives a nice bounce effect, and you can play with the parameters to adjust it accordingly.
However, because of the nature of the input, I'm not sure how the animation will fare.
It might be best to update the audioCircle size every so often, as opposed to constantly. This would allow the animation time to preform both correctly and smoothly as opposed to rigidly.
Good luck.

Swift UIView Animation block increment error

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]
})

Resources