10

When doing UIView.animateWithDuration, I would like to define a custom curve for the ease, as opposed to the default: .CurveEaseInOut, .CurveEaseIn, .CurveEaseOut, .CurveLinear.

This is an example ease that I want applied to UIView.animateWithDuration:

let ease = CAMediaTimingFunction(controlPoints: Float(0.8), Float(0.0), Float(0.2), Float(1.0))

I tried making my own UIViewAnimationCurve, but it seems it accepts only one Int.

I can apply the custom ease to a Core Animation, but I would like to have custom easing for UIView.animateWithDuration for simpler and optimized code. UIView.animateWithDuration is better for me as I won't have to define animations for each animated property and easier completion handlers, and to have all animation code in one function.

Here's my non-working code so far:

let customOptions = UIViewAnimationOptions(UInt((0 as NSNumber).integerValue << 50))
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: 5)!)

UIView.animateWithDuration(2, delay: 0, options: customOptions, animations: {
        view.layer.position = toPosition
        view.layer.bounds.size = toSize
        }, completion: { finished in
            println("completion")
})
rmaddy
  • 314,917
  • 42
  • 532
  • 579
brandon koopa
  • 103
  • 1
  • 4
  • Take a look at this question. Most of the answer are in objective-c but there are a bunch of options http://stackoverflow.com/q/5161465/4114683 – TheSD Jul 08 '15 at 20:02
  • Great info on timing functions. This helps a lot too: http://netcetera.org/camtf-playground.html However, these are all for CA Animation (which does need to be used to make custom curves). OP wanted to add these curves to a UIView animation block. – Chris Slowik Jul 08 '15 at 20:05
  • So far, it sounds like my answer is NO. – brandon koopa Jul 08 '15 at 20:56
  • You can accomplish something similar using `UIView.animateKeyframesWithDuration`. That's an inelegant way to solve it, but would work. But if your question is "can I just apply my own timing function to UIKit block-based animations", then the answer is no. – Rob Jul 09 '15 at 01:53

3 Answers3

3

That's because UIViewAnimationCurve is an enumeration - its basically human-readable representations of integer values used to determine what curve to use.

If you want to define your own curve, you need to use CA animations.

You can still do completion blocks and groups of animations. You can group multiple CA Animations into a CAAnimationGroup

let theAnimations = CAAnimationGroup()
theAnimations.animations = [positionAnimation, someOtherAnimation]

For completion, use a CATransaction.

CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
    // something?
}
// your animations go here
CATransaction.commit()
Chris Slowik
  • 2,859
  • 1
  • 14
  • 27
  • Chris is right. You can't use custom easing curves with `UIView` animation methods in the `animateWithDuration` family. (At least not without some trickery where you modify the CAAnimation that gets created.) Core Animation is both much more powerful and much more complicated to use than UIView animation. – Duncan C Jul 08 '15 at 20:09
  • If I have a followup animation, is it best to put that animation in the setCompletionBlock? And if a third animation, then another animation in THAT setCompletionBlock? 3-step animation will be very common for me, each with anticipation and overshoot. – brandon koopa Jul 08 '15 at 20:16
  • Honestly, i'd avoid using completion blocks for something like that, just because its messy for no additional benefit. If you know the timing of the animation I would say just set your `beginTime` for each animation appropriately. Something like: `myAnimation.beginTime = 1` Like Duncan said, its much more powerful and flexible. Plus, if you have some common animations in your app, you can set them up in a category and have short access like: `myLayer.addAnimation(CABasicAnimation.addOpacityAnimationWithDuration(1), forKey: "opacity")` – Chris Slowik Jul 08 '15 at 20:19
  • Ah, very true, beginTime is great for that. Thanks! About setCompletionBlock, is it still handy when you want to do non-animation stuff when the animation is complete? – brandon koopa Jul 08 '15 at 20:22
  • Yup! You can actually do whatever you want in there. That said, if there's a lot of stuff to be done, you should probably contain it within a helper function and simply call it from your completion block. Especially if those actions have to do with data manipulation - best to separate that from the animation code for cleanliness and readability, and respect separation of display from data. – Chris Slowik Jul 08 '15 at 20:57
  • Thank you for the info on Core Animation, Chris. I'll have to accept that a custom ease is not possible with UIView.animateWithDuration – brandon koopa Jul 08 '15 at 21:09
  • As for anticipation and overshoot, I could include that in the custom ease, except it seems to ignore numbers below 0 and above 1, like so: let easing = CAMediaTimingFunction(controlPoints: Float(0.8), Float(-0.3), Float(0.2), Float(1.3)) – brandon koopa Jul 08 '15 at 21:43
  • I just tried this timing function to test, and it anticipates and overshoots: `yourAnimationVariable.timingFunction = CAMediaTimingFunction(controlPoints: 0.85, -0.35, 0.35, 1.35)` (by the way you don't need to cast those floats) – Chris Slowik Jul 08 '15 at 22:00
  • Aha! It didn't work when I had the timingFunction on the animationGroup, but it works on each animation. Thanks again! – brandon koopa Jul 09 '15 at 18:50
  • Whoa that is really weird. I'll have to do some testing on that! – Chris Slowik Jul 09 '15 at 18:51
3

After a bunch of researches, the following code works for me.

  1. Create an explicit core animation transaction, set your desired timing function.
  2. Use eigher or both UIKit and CAAnimation to perform the changes.

    let duration = 2.0
    
    CATransaction.begin()
    CATransaction.setAnimationDuration(duration)
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(controlPoints: 0.8, 0.0, 0.2, 1.0))
    CATransaction.setCompletionBlock {
        print("animation finished")
    }
    
    // View animation
    UIView.animate(withDuration: duration) {
        // E.g. view.center = toPosition
    }
    
    // Layer animation
    view.layer.position = toPosition
    view.layer.bounds.size = toSize
    
    CATransaction.commit()
    
夜一林风
  • 1,247
  • 1
  • 13
  • 24
2

With iOS 10.0 you can use UIViewPropertyAnimator.

https://developer.apple.com/documentation/uikit/uiviewpropertyanimator

See https://developer.apple.com/videos/play/wwdc2016/216/

Jonny
  • 15,955
  • 18
  • 111
  • 232