I am new to coding in Swift. I am working with animations, and I have a problem.
UIView.animate(withDuration: TimeInterval(waitTime), delay: 0, options: [.allowUserInteraction, .allowAnimatedContent, .curveLinear, .repeat], animations: {
UIView.setAnimationRepeatCount(Float(self.clickAccuracy))
// Animate tiles going down
self.tileButton1.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton2.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton3.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton4.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton5.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
})
Here, I want to repeat an animation of a UIButton moving down. (I have to repeat the animation in smaller blocks because I want to be able to click on the moving button). However, the buttons are going down as far as they are meant to, but instead of repeating again from that position, it resets. This causes an infinite flicker.
All I need is for the buttons to move down, without the position restarting to its original position, so I can have a smooth animation. Thanks!
Here is a video
https://i.imgflip.com/27uzl3.gif
Turns out, the original way I did didn't seem to work. So I changed it to this:
var numberOfIterations = 0
func repeatAnimation() {
UIView.animate(withDuration: TimeInterval(waitTime), delay: 0, options: [.allowUserInteraction, .allowAnimatedContent, .curveLinear, .curveLinear], animations: {
//UIView.setAnimationRepeatCount(Float(self.clickAccuracy))
// Animate tiles going down
self.tileButton1.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton2.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton3.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton4.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
self.tileButton5.center.y += self.tileButtonExample.frame.height / CGFloat(self.clickAccuracy)
}, completion: { (Finished) in
numberOfIterations += 1
// Repeat
if numberOfIterations != self.clickAccuracy {
self.repeatAnimation()
}
})
}
When #rmaddy gave me this answer, we forgot to remove the setAnimationRepeatCount (commented out). To make this repeat a set number of times, I put an if statement in the completion block.
I also got rid of .repeat in the animation options.
Thanks for everyone's help!
Related
I investigated but I could not find anything like this for Swift. Can you please help me make this animation in Swift?
Val animador = ValueAnimator.ofFloat(0.0f ,1.0f)
animador.repeatCount = ValueAnimator.INFINITE
animador.interpolator= LinearInterpolator()
animador.duration = 10000L
animador.addUpdateListener { animation ->
val progreso = animador.animatedValue as Float
Val anchura = fondo!!.width
fondo!!.traslationx = transicionX
fondo2!!.traslationx = transicion X -anchura
}
animador.start()
This may not be exactly right, but try this:
let anchura = fondo.frame.width
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
fondo.frame.origin.x = CGFloat(transicionX)
fondo2.frame.origin.x = CGFloat(transicionX) - anchura
})
Swift animations start automatically (no need to start() or anything).
Edit:
Inside the animations block, you just put the final value that you want. This means that you don't need to calculate the progress or anything, and you probably don't even need a transicionX variable. Instead, try this:
let anchura = fondo.frame.width /// let's say this is 50
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .autoreverse], animations: {
fondo.frame.origin.x += anchura /// move fondo to the right by 50
fondo2.frame.origin.x -= anchura /// move fondo2 to the left by 50
})
Another edit: made it += and -= to animate on top of existing values
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.
I'm trying to animate the heading line of a naval-style radar (the spinning part below) in iOS, like this:
My current slow, laggy, and high-overhead solution (pseudocode because the real Swift code is a little lengthy):
create NSTimer to call animate() every 1/60 sec
currentAngle = 0
animate():
create UIBezierPath from center of circle to outside edge at currentAngle
add path to new CAShapeLayer, add layer to views layer
add fade out CABasicAnimation to layer (reduces opacity over time)
increase currentAngle
What's the best way to do this without using a .gif? I have implemented the solution above, but the performance (frame rate and CPU use) is awful. How would you approach this problem?
Your approach is too complex. Don't try to re-draw your image during the animation.
Separate out the part that rotates from the part that fades in and out and animate them separately. Make each animation draw partly transparent so the other animation shows through.
Either create a static image of the part that rotates, or build a layer that creates that image.
If you create an image, you can use UIView animation to animate the rotation on the image view.
If you create a layer that draws the heading line and gradient around it, then use a CABasicAnimation that animates the z rotation of the layer's transform.
I've come to a working Swift 3 solution, thanks to Animations in Swift / iOS 8 article :
let scanInProgress = UIImageView()
scanInProgress.image = UIImage(named: "scanInProgress.png")
scanInProgress.frame = CGRect(x: 150, y: 200, width: 80, height: 200)
view.addSubview(scanInProgress)
let fullRotation = CGFloat(Double.pi * 2)
let duration = 2.0
let delay = 0.0
UIView.animateKeyframes(withDuration: duration, delay: delay, options: [.repeat, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 1/3 * fullRotation)
})
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 2/3 * fullRotation)
})
UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 3/3 * fullRotation)
})
}, completion: {
})
I wanted to make some tests. I thought, let's make an animation for a cell in a UITableView such as: it goes from the normal size, expand horizontally a bit over the normal size, reduce horizontally a bit under the normal size and go back to the normal size. So I thought make this:
let width = cell.frame.size.width
cell.frame = CGRectMake(0, cell.frame.origin.y, width, cell.frame.size.height)
UIView.animateWithDuration(4.25, animations: {
cell.frame = CGRectMake(width * -1/8, cell.frame.origin.y, width * 5/4, cell.frame.size.height)
}, completion: { finished in
UIView.animateWithDuration(2.5, animations: {
cell.frame = CGRectMake(width * 1/8, cell.frame.origin.y, width * 3/4, cell.frame.size.height)
}, completion: { finished in
UIView.animateWithDuration(1.25) {
cell.frame = CGRectMake(0, cell.frame.origin.y, width, cell.frame.size.height)
}
})
})
Use animations and completions in cascades. The problem is at the beginning. The frame reduces quickly horizontally (instead of expanding) then slowly (4.25 seconds) expands to the normal size. Then it reduces and then expand back to normal as expected.
Am I doing something wrong?
I was actually fiddling with animations just like the one you were doing just now :)
You can do the horizontal effect much easier; Instead of redefining the frame itself, you can simply adjust the horizontal bounds of the cell:
cell.bounds.size.width += 30 //grow it horizontally by 30 points
There's also a much more succinct method in dealing with this problem: UIView.animateKeyframesWithDuration(_ delay: options: animations: completion:)
With animateKeyframes, you can string a list of different animation effects in order, which is what you want. I'll start you off:
UIView.animateKeyframesWithDuration(4.0, delay: 0.0, options: nil, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.25, animations: {
self.cell.bounds.size.width += 30
})
UIView.addKeyframeWithRelativeStartTime(2.5, relativeDuration: 0.1, animations: {
self.cell.bounds.size.width -= 60
})
UIView.addKeyframeWithRelativeStartTime(3.0, relativeDuration: 0.1, animations: {
self.cell.bounds.size.width += 30
})
}, completion: nil)
To put it simply, you define a duration for which the animation will occur. For the above code sample, I've defined 4 seconds.
Within the 4 seconds, I defined 3 different animations, specifying at what time they should fire off, and how long they should be fired for (relativeDuration, which is relative to the 4.0 second value).
Adjusting those numbers will allow you to customize the timings.
Hope that works!
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]
})