9

I tried to use the code below to create a continuously rotating square on the screen. But I don't know why the rotational speed is changing. How could I change the code to make the rotational speed invariable? I tried different UIViewKeyframeAnimationOptions, but seems none of them work.

override func viewDidLoad() {
    super.viewDidLoad()

    let square = UIView()
    square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
    square.backgroundColor = UIColor.redColor()
    self.view.addSubview(square)

    let duration = 1.0
    let delay = 0.0
    let options = UIViewKeyframeAnimationOptions.Repeat
        UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
        let fullRotation = CGFloat(M_PI * 2)

        UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
            square.transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
        })                        
        UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
            square.transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
        })                        
        UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
            square.transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
        })
        }, completion: {finished in
        })
    }         
Dharmesh Kheni
  • 71,228
  • 33
  • 160
  • 165
NixiliaAK
  • 93
  • 1
  • 4
  • 2
    1/3 gives a different result to 1.0/3.0. Is that the problem? – InsertWittyName Oct 30 '14 at 23:27
  • Tried to change the 9th line to `let options = UIViewKeyframeAnimationOptions.CalculationModeLinear|UIViewKeyframeAnimationOptions.Repeat` but does not help – NixiliaAK Oct 31 '14 at 00:19
  • Tried to change all 1/3 to 1.0/3.0 . but does not help – NixiliaAK Oct 31 '14 at 00:21
  • Is there a reason you are using Keyframe? would a UIView.animateWithDuration(duration:delay:options:animations:completion:) work. I also had trouble with the keyframe version, but got this version to work. – JMFR Oct 31 '14 at 01:48
  • @JMFR I'm using Keyframe since I found animateWithDuration won't work. Because the start and end state of a full 360' rotation are equivalent, Swift did not interpolate anything in between. Could you please provide more details if you got the animateWithDuration version work? – NixiliaAK Oct 31 '14 at 02:06
  • @NixiliaAK I will post the animateWithDuration source later today. – JMFR Oct 31 '14 at 16:46

4 Answers4

10

I faced same problem before, this is how I make it work:

Swift 2

let raw = UIViewKeyframeAnimationOptions.Repeat.rawValue | UIViewAnimationOptions.CurveLinear.rawValue
let options = UIViewKeyframeAnimationOptions(rawValue: raw)

Swift 3,4,5

let raw = UIView.KeyframeAnimationOptions.repeat.rawValue | UIView.AnimationOptions.curveLinear.rawValue
let options = UIView.KeyframeAnimationOptions(rawValue: raw)

I figured this problem out just before gave it up. I don't think there is doc about it, but it just work.

Karen Hovhannisyan
  • 1,140
  • 2
  • 21
  • 31
Alan Zhang
  • 121
  • 7
4

That's really odd... UIView.animateKeyframesWithDuration isn't working as I would expect it to with UIViewKeyframeAnimationOptions.CalculationModeLinear|UIViewKeyframeAnimationOpti‌​ons.Repeat passed in with options.

If you use the non-block method of creating a keyframe animation (see below) the rotation repeats as expected.

If I find out why the block-based option isn't working I'll try and remember to update answer here too!

override func viewDidLoad() {
    super.viewDidLoad()


    let square = UIView()
    square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
    square.backgroundColor = UIColor.redColor()
    self.view.addSubview(square)

    let fullRotation = CGFloat(M_PI * 2)

    let animation = CAKeyframeAnimation()
    animation.keyPath = "transform.rotation.z"
    animation.duration = 2
    animation.removedOnCompletion = false
    animation.fillMode = kCAFillModeForwards
    animation.repeatCount = Float.infinity
    animation.values = [fullRotation/4, fullRotation/2, fullRotation*3/4, fullRotation]

    square.layer.addAnimation(animation, forKey: "rotate")

}
MathewS
  • 2,267
  • 2
  • 20
  • 31
1

Add UIViewKeyframeAnimationOptionCalculationModeLinear do your keyframe options. The default behavior for UIView animations is to "ease in/out" of the animation. i.e., start slow, go up to speed, then slow down again just near the end.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • 2
    Thanks for your answer. I changed the options to " let options = UIViewKeyframeAnimationOptions.CalculationModeLinear|UIViewKeyframeAnimationOptions.Repeat" but seems got the same result. – NixiliaAK Oct 31 '14 at 00:17
0

AFAIU, this is happening because the default animation curve is UIViewAnimationOption.CurveEaseInOut.

Unfortunately, UIViewKeyframeAnimationOptions doesn't have options for changing the curve, but you can add them manually!

Use this extension:

Swift 2

extension UIViewKeyframeAnimationOptions {
    static var CurveEaseInOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseInOut.rawValue) }
    static var CurveEaseIn: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseIn.rawValue) }
    static var CurveEaseOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseOut.rawValue) }
    static var CurveLinear: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveLinear.rawValue) }
}

Swift 3, 4, 5

extension UIView.KeyframeAnimationOptions {
    static var curveEaseInOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseInOut.rawValue) }
    static var curveEaseIn: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseIn.rawValue) }
    static var curveEaseOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseOut.rawValue) }
    static var curveLinear: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue) }
}

Now you can use curve options in UIView.animateKeyframesWithDuration method

Swift 2

let keyframeOptions: UIViewKeyframeAnimationOptions = [.Repeat, .CurveLinear]
UIView.animateKeyframesWithDuration(duration, delay: delay, options: keyframeOptions, animations: {
    // add key frames here
}, completion: nil)

Swift 3, 4, 5

let keyframeOptions: UIView.KeyframeAnimationOptions = [.repeat, .curveLinear]
UIView.animateKeyframes(withDuration: duration, delay: delay, options: keyframeOptions, animations: {
    // add key frames here
}, completion: nil)
App Dev Guy
  • 5,396
  • 4
  • 31
  • 54
Anton
  • 726
  • 11
  • 23