6

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))
}
Community
  • 1
  • 1
David James
  • 2,430
  • 1
  • 26
  • 35
  • 1
    180 Clockwise: `let radians = Angle(180).radians` 180 Conter clockwise: `let radians = Angle(-540).radians` – Arvis Aug 11 '17 at 09:09

4 Answers4

4

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)
    }
Andrey M.
  • 3,021
  • 29
  • 42
2

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")
David James
  • 2,430
  • 1
  • 26
  • 35
1
    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")
    }
Anit Kumar
  • 8,075
  • 1
  • 24
  • 27
0

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)
            })
        })
Jose Santos
  • 697
  • 1
  • 7
  • 10