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: {
})
Related
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.
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.
Im struck with Animation. I would like to animate in below sequence as shown in picture.
Please click here for Image
All are views i.e., outerView, dot1, dot2, dot3 . I've implemented code to animate dots but need your help to animate outerview and adding everything in sequence
let transition = CATransition()
transition.duration = 2;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
transition.speed = 1.0
dot3?.layer.add(transition, forKey: nil)
transition.beginTime = CACurrentMediaTime() + 0.11
dot2?.layer.add(transition, forKey: nil)
transition.beginTime = CACurrentMediaTime() + 0.22
dot1?.layer.add(transition, forKey: nil)
Please help me animating in sequence - outerView starting, dots and closing outerView like shown
You're on the right path, except obviously there will be a lot more animations than the few that you've shown in the snippet. There's no reason why you can't continue building this animation using the CAAnimation classes, but I suspect that using the newer UIViewPropertyAnimator classes (will need to target iOS10) will be useful because they allow you to 'scrub' the steps in the animation which will be useful debugging. Here's a good intro: dzone.com/articles/ios-10-day-by-day-uiviewpropertyanimator
Expanding on this comment to a proper answer...
Using animateWithKeyframes is a pretty decent solution to create this animation in code. Here's a snippet of what this could look like:
let capsule: UIView // ... the container view
let capsuleDots [UIView] //... the three dots
let capsuleFrameWide, capsuleFrameNarrow: CGRect //.. frames for the capsule
let offstageLeft, offstageRight: CGAffineTransform // transforms to move dots to left or right
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn)
// the actual animation occurs in 4 steps
animator.addAnimations {
UIView.animateKeyframes(withDuration: 2, delay: 0, options: [.calculationModeLinear], animations: {
// step 1: make the capsule grow to large size
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.1) {
capsule.bounds = capsuleFrameWide
}
// step 2: move the dots to their default positions, and fade in
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.1) {
capsuleDots.forEach({ dot in
dot.transform = .identity
dot.alpha = 1.0
})
}
// step 3: fade out dots and translate to the right
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 0.1) {
capsuleDots.forEach({ dot in
dot.alpha = 0.0
dot.transform = offstageRight
})
}
// step4: make capsure move to narrow width
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1) {
capsule.bounds = capsuleFrameNarrow
}
})
}
Wrapping the keyframes in a UIViewPropertyAnimator makes it easy to scrub the animation (among other things).
In case it's useful for anyone, I've pushed a small project to GitHub that allows you to jump in an explore/refine/debug animations with UIViewPropertyAnimator. It includes boilerplate for connecting the UISlider to the animation so all you have to focus on is the animation itself.
This is all for debugging the animation, for production of course you'll probably want to remove hard coded sizes so it can be potentially reused at different scales etc.
It very easy to implement
animatedImage(with:duration:)
or
var animationImages: [UIImage]?
example:
UIImageView.animationImages = [image1, image2, image3, image4,...]
UIImageView.animationDuration = 5
UIImageView.startAnimating()
You will get ordered animation with couple of lines only
I am trying to animate the opacity of a CALayer but the values are more than one value to begin with and one to end with.
For example: I want it to animate throw these values: 0.0, 0.7, 0.3, 1.0, 0.5, 0.0
I also want the animation to repeat with auto reverse.
This is what I have for now:
let redLineAnimation = CABasicAnimation(keyPath: "opacity")
redLineAnimation.duration = 0.4
redLineAnimation.autoreverses = true
redLineAnimation.fromValue = 0.0
redLineAnimation.toValue = 1.0
redLineAnimation.repeatCount = Float.infinity
movingRedLineLayer.addAnimation(redLineAnimation, forKey: nil)
I am new to iOS development. What can I do? Thanks!
Take a look at CAKeyframeAnimation. That will give you what you want.
And if what you are animating is a view's layer, you could use the easier-to-use UIView keyframe based animation methods. (Core Animation is pretty tricky, and the docs are spotty)
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!