According to the documentation for rotated(by:):
angle
The angle, in radians, by which to rotate the affine transform. In iOS, a positive value specifies counterclockwise rotation and a negative value specifies clockwise rotation.
This has caused much confusion and has been partly answered, but even if we accept that the coordinate system is flipped (and just do the opposite of what the documentation says), it still provides inconsistent results when animating.
The direction of rotation -- clockwise or counterclockwise -- depends on the proximity of the target rotation to the current rotation:
<= 180º animates clockwise
> 180º animates counter-clockwise
Using UIView.animate or UIViewPropertyAnimator shows the inconsistency:
// Animates CLOCKWISE
UIView.animate(withDuration: 2.0) {
let radians = Angle(179).radians // 3.12413936106985
view.transform = view.transform.rotated(by: CGFloat(radians))
}
// Animates COUNTER-CLOCKWISE
UIViewPropertyAnimator(duration: 2, curve: .linear) {
let radians = Angle(181).radians // 3.15904594610974
view.transform = view.transform.rotated(by: CGFloat(radians))
}
// Does not animate
UIViewPropertyAnimator(duration: 2, curve: .linear) {
let radians = Angle(360).radians // 6.28318530717959
view.transform = view.transform.rotated(by: CGFloat(radians))
}
Try this:
let multiplier: Double = isSelected ? 1 : -2
let angle = CGFloat(multiplier * Double.pi)
UIView.animate(withDuration: 0.4) {
self.indicatorImageView.transform = CGAffineTransform(rotationAngle: angle)
}
The answer is to use CABasicAnimation for rotations past 180º, keeping in mind that positive values are clockwise and negative values are counterclockwise.
// Rotate 360º clockwise.
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = Angle(0).radians
rotate.toValue = Angle(360).radians
rotate.duration = 2.0
self.layer.add(rotate, forKey: "transform.rotation")
if sender.isSelected {
/// clockwise
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = 0
rotate.toValue = CGFloat.pi
rotate.duration = 0.25
rotate.fillMode = CAMediaTimingFillMode.forwards
rotate.isRemovedOnCompletion = false
self.arrowButton.layer.add(rotate, forKey: "transform.rotation")
} else {
/// anticlockwise
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = CGFloat.pi
rotate.toValue = 0
rotate.duration = 0.25
rotate.fillMode = CAMediaTimingFillMode.forwards
rotate.isRemovedOnCompletion = false
self.arrowButton.layer.add(rotate, forKey: "transform.rotation")
}
The third option allows you to rotate over 180º in the desired direction:
// #1 This rotates 90º clockwise
UIView.animate(withDuration: 0.25, animations:{
view.transform = CGAffineTransform(rotationAngle: -CGFloat.pi/2)
})
// #2 This rotates 90º counter clockwise back to #1
UIView.animate(withDuration: 0.25, animations:{
view.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
})
// #3 This rotates 270º clockwise back to #1
UIView.animate(withDuration: 0.1875, animations:{
view.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
}, completion: { _ in
UIView.animate(withDuration: 0.0625, animations:{
view.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
})
})
Related
I am using this code to rotate a view 360 degrees infinitely. But my view is not rotating:
func rotateImageView() {
UIView.animate(withDuration: 3.0, delay: 0, options: [.repeat, .curveLinear], animations: {
self.vinylView.transform = CGAffineTransform(rotationAngle: .pi * 2)
})
}
How to fix it?
Substitute pi * with /, delete repeat, add completion and recall the function like this:
private func rotateImageView() {
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
self.vinylView.transform = self.vinylView.transform.rotated(by: .pi / 2)
}) { (finished) in
if finished {
self.rotateImageView()
}
}
}
Solution using CABasicAnimation
// Rotate vinvlView
vinylView.layer.add(CABasicAnimation.rotation, forKey: nil)
extension CABasicAnimation {
static let rotation : CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.repeatCount = .infinity // Rotate a view 360 degrees infinitely
animation.fromValue = 0
animation.toValue = CGFloat.pi * 2
animation.duration = 3.0
return animation
}()
}
I am rotating image through this code
UIView.animate(withDuration: 2) {
self.blueNeedleImageView.transform = CGAffineTransform(rotationAngle: CGFloat(myDegreeValue * Double.pi / 180))
}
Problem is that it is rotating in shortest path. I want always clockwise rotation and rotation degree is variable. this and this does not provide a solution.
Just split it to two animations if the angle is more than 180°.
You can rotate your Image as per your requirement with the help of CoreAnimation like below:
let angle = myDegreeValue
if let n = NumberFormatter().number(from: angle) {
let floatAngle = Double(truncating: n)
CATransaction.begin()
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.byValue = NSNumber(value: floatAngle * (Double.pi / 180))
rotationAnimation.duration = 2
rotationAnimation.isRemovedOnCompletion = true
CATransaction.setCompletionBlock {
self.blueNeedleImageView.transform = CGAffineTransform(rotationAngle: CGFloat(floatAngle * (Double.pi / 180)))
}
self.blueNeedleImageView.layer.add(rotationAnimation, forKey: "rotationAnimation")
CATransaction.commit()
}
I'm rotating a the minute pointer on my watch icon, but at the end of the animation, it jumps back to the starting position. How can I have it stay where it stopped?
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = Double.pi
rotation.duration = 0.5 // or however long you want ...
rotation.isCumulative = true
watchIconMin.layer.add(rotation, forKey: "rotationAnimation")
Use UIView animation if watchIconMin is a subclass or class of UIView.
UIView.animate(withDuration: 0.5, animations: {
self.watchIconMin.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}) { (success) in
self.watchIconMin.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}
I'm having an animation that it supposed to rotate an image constantly. But there are couple issues with it. The velocity is quite odd and despite I've set it to repeat constantly, you can see how it starts, stops and then repeats. Which should not happen. It should be uninterrupted rotating.
Also, the other problem is when the animation stops, the image moves left for some reason.
Here's my code:
func animateLogo()
{
UIView.animate(withDuration: 6.0, delay: 0.0, options: .repeat, animations: {
self.logo.transform = CGAffineTransform(rotationAngle: ((180.0 * CGFloat(Double.pi)) / 180.0))
}, completion: nil)
}
Try this
func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(M_PI))
}) { finished in
self.rotateView(targetView: YOUR_LOGO, duration: duration)
}
}
How to use
self.rotateView(targetView: YOUR_LOGO, duration: duration)
In iOS, the coordinate system is flipped. So you go clockwise as your degree gains. It means that passing 270° will give you an angle, equivalent to 90° in the standard coordinate system. Keep that in mind and provide needed angle accordingly.
Consider the following approach.
1) Handy extension for angle
postfix operator °
protocol IntegerInitializable: ExpressibleByIntegerLiteral {
init (_: Int)
}
extension Int: IntegerInitializable {
postfix public static func °(lhs: Int) -> CGFloat {
return CGFloat(lhs) * .pi / 180
}
}
extension CGFloat: IntegerInitializable {
postfix public static func °(lhs: CGFloat) -> CGFloat {
return lhs * .pi / 180
}
}
2) Rotate to any angle with CABasicAnimation:
extension UIView {
func rotateWithAnimation(angle: CGFloat, duration: CGFloat? = nil) {
let pathAnimation = CABasicAnimation(keyPath: "transform.rotation")
pathAnimation.duration = CFTimeInterval(duration ?? 2.0)
pathAnimation.fromValue = 0
pathAnimation.toValue = angle
pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
self.transform = transform.rotated(by: angle)
self.layer.add(pathAnimation, forKey: "transform.rotation")
}
}
Usage:
override func viewDidAppear(_ animated: Bool) {
// clockwise
myView.rotateWithAnimation(angle: 90°)
// counter-clockwise
myView.rotateWithAnimation(angle: -270°)
}
Passing negative value will rotate counter-clockwise.
Angles should be in radians and not degrees. Angle in radians = degrees * pi / 180. So if you want to rotate by 360 degrees you should enter radians = 360 * pi / 180 = 2 * pi = 2 * 3.1415 = 6.283.
I want to animate a UIImageView. The image should only spin clockwise. When the image is spinning, I want to speed it up. When I do speed it up, it goes counterclockwise and the start position is not correct as well. This is my code:
func rotateAnimation(theView: UIView, duration: CFTimeInterval = 2.0, repeatCount: Float = 1, toValue: CGFloat = CGFloat(.pi * 2.0), linear: Bool = false, animation: String = "transform.rotation") {
let rotateAnimation = CABasicAnimation(keyPath: animation)
let currentLayer = (theView.layer.presentation())
var currentAngle = CFloat((currentLayer?.value(forKeyPath: "transform.rotation") as? NSNumber)!)
currentAngle = roundf(currentAngle * 180 / Float(Double.pi))
print(currentAngle)
rotateAnimation.fromValue = currentAngle
if !linear{
rotateAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
}
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration
rotateAnimation.repeatCount = repeatCount
theView.layer.add(rotateAnimation, forKey: nil)
}
The print shows me to correct angle.
I am calling this method below twice. The second time I am calling it, the image is spinning counterclock:
rotateAnimation(theView: star, duration: 15.0, repeatCount: Float.infinity, toValue: CGFloat(Double.pi * 2), linear: true)
delay(delay: 3.5, closure: {
self.rotateAnimation(theView: self.star, duration: 7, repeatCount: Float.infinity, toValue: CGFloat(Double.pi * 2), linear: true)
})