51

The UIKeyboardAnimationCurveUserInfoKey has a UIViewAnimationCurve value. How do I convert it to the corresponding UIViewAnimationOptions value for use with the options argument of +[UIView animateWithDuration:delay:options:animations:completion:]?

// UIView.h

typedef enum {
    UIViewAnimationCurveEaseInOut,         // slow at beginning and end
    UIViewAnimationCurveEaseIn,            // slow at beginning
    UIViewAnimationCurveEaseOut,           // slow at end
    UIViewAnimationCurveLinear
} UIViewAnimationCurve;

// ...

enum {
    // ...
    UIViewAnimationOptionCurveEaseInOut            = 0 << 16, // default
    UIViewAnimationOptionCurveEaseIn               = 1 << 16,
    UIViewAnimationOptionCurveEaseOut              = 2 << 16,
    UIViewAnimationOptionCurveLinear               = 3 << 16,
    // ...
};
typedef NSUInteger UIViewAnimationOptions;

Obviously, I could create a simple category method with a switch statement, like so:

// UIView+AnimationOptionsWithCurve.h

@interface UIView (AnimationOptionsWithCurve)
@end

// UIView+AnimationOptionsWithCurve.m

@implementation UIView (AnimationOptionsWithCurve)

+ (UIViewAnimationOptions)animationOptionsWithCurve:(UIViewAnimationCurve)curve {
    switch (curve) {
        case UIViewAnimationCurveEaseInOut:
            return UIViewAnimationOptionCurveEaseInOut;
        case UIViewAnimationCurveEaseIn:
            return UIViewAnimationOptionCurveEaseIn;
        case UIViewAnimationCurveEaseOut:
            return UIViewAnimationOptionCurveEaseOut;
        case UIViewAnimationCurveLinear:
            return UIViewAnimationOptionCurveLinear;
    }
}

@end

But, is there an even easier/better way?

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
ma11hew28
  • 121,420
  • 116
  • 450
  • 651

5 Answers5

45

The category method you suggest is the “right” way to do it—you don’t necessarily have a guarantee of those constants keeping their value. From looking at how they’re defined, though, it seems you could just do

animationOption = animationCurve << 16;

...possibly with a cast to NSUInteger and then to UIViewAnimationOptions, if the compiler feels like complaining about that.

Noah Witherspoon
  • 57,021
  • 16
  • 130
  • 131
  • 9
    I recommend guarding that with something like `NSAssert(UIViewAnimationCurveLinear << 16 == UIViewAnimationOptionCurveLinear, @"Unexpected implementation of UIViewAnimationCurve");` – lhunath Mar 23 '14 at 00:35
  • 2
    @lhunath 7 is used for keyboard animation and is private. But passed with userInfo from keyboard. – pronebird Nov 07 '16 at 13:59
37

Arguably you can take your first solution and make it an inline function to save yourself the stack push. It's such a tight conditional (constant-bound, etc) that it should compile into a pretty tiny piece of assembly.

Edit: Per @matt, here you go (Objective-C):

static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve)
{
  switch (curve) {
    case UIViewAnimationCurveEaseInOut:
        return UIViewAnimationOptionCurveEaseInOut;
    case UIViewAnimationCurveEaseIn:
        return UIViewAnimationOptionCurveEaseIn;
    case UIViewAnimationCurveEaseOut:
        return UIViewAnimationOptionCurveEaseOut;
    case UIViewAnimationCurveLinear:
        return UIViewAnimationOptionCurveLinear;
  }
}

Swift 3:

extension UIViewAnimationOptions {
    init(curve: UIViewAnimationCurve) {
        switch curve {
            case .easeIn:
                self = .curveEaseIn
            case .easeOut:
                self = .curveEaseOut
            case .easeInOut:
                self = .curveEaseInOut
            case .linear:
                self = .curveLinear
        }
    }
}
kelin
  • 11,323
  • 6
  • 67
  • 104
David Pisoni
  • 3,317
  • 2
  • 25
  • 35
  • How do I do that? I thought LLVM automatically converts Objective-C methods into inline functions when possible. – ma11hew28 Nov 19 '11 at 13:33
  • Sounds like someone else already answered your question: http://stackoverflow.com/questions/8194504/does-llvm-convert-objective-c-methods-to-inline-functions – David Pisoni Nov 20 '11 at 20:12
  • I added the inline version to my answer. – David Pisoni Nov 20 '11 at 20:14
  • Aren't you missing the breaks in your switch statement? – Florian May 13 '13 at 16:26
  • 1
    "return" happens immediately, so no. – David Pisoni May 14 '13 at 05:33
  • 15
    This is not good way to do this. `UIKeyboardWillShowNotification`'s `[userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]` returns 7. There's no such case in this switch. Check out the top-voted answer. – derpoliuk Jan 12 '14 at 17:55
  • I would fear an implicit match like that. Would have no idea if Apple will some day create constants that won't match the shift. Still, this approach clearly has the hole of not supporting new types. – David Pisoni Jan 13 '14 at 19:30
  • I'm seeing 7 for the animation curve on iOS 7.1 See http://stackoverflow.com/questions/18837166/how-to-mimic-keyboard-animation-on-ios-7-to-add-done-button-to-numeric-keyboar for a better way. Weird thing happen with this code, as there is no default in the switch. – Seth Spitzer Mar 11 '14 at 19:45
19

In Swift you can do

extension UIViewAnimationCurve {
    func toOptions() -> UIViewAnimationOptions {
        return UIViewAnimationOptions(rawValue: UInt(rawValue << 16))
    }
}
Tomáš Linhart
  • 13,509
  • 5
  • 51
  • 54
11

An issue with the switch based solution is that it assumes no combination of options will be ever passed in. Practice shows though, that there may be situations where the assumption doesn't hold. One instance I found is (at least on iOS 7) when you obtain the keyboard animations to animate your content along with the appearance/disappearance of the keyboard.

If you listen to the keyboardWillShow: or keyboardWillHide: notifications, and then get the curve the keyboard announces it will use, e.g:

UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];

you're likely to obtain the value 7. If you pass that into the switch function/method, you won't get a correct translation of that value, resulting in incorrect animation behaviour.

Noah Witherspoon's answer will return the correct value. Combining the two solutions, you might write something like:

static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve)
{
    UIViewAnimationOptions opt = (UIViewAnimationOptions)curve;
    return opt << 16;
}

The caveat here, as noted by Noah also, is that if Apple ever changes the enumerations where the two types no longer correspond, then this function will break. The reason to use it anyway, is that the switch based option doesn't work in all situations you may encounter today, while this does.

Antonio Nunes
  • 166
  • 1
  • 8
  • 2
    If you are getting a value of 7 for the UIKeyboardAnimationCurveUserInfoKey, that sounds like a bug. The documentation states that the value is a UIViewAnimationCurve. UIViewAnimationCurve is defined as an NS_ENUM, not [NS_OPTIONS](http://nshipster.com/ns_enum-ns_options/). Therefore, the only possible values are 0, 1, 2, and 3. Any other value is meaningless. UIViewAnimationOption, on the other hand, is defined as NS_OPTIONS, and can have about 32,000 different values. Not even Noah's solution can handle a value of 7, it will simply treat it as though UIViewAnimationCurveLinear was passed in. – Senseful Oct 03 '15 at 23:49
5

iOS 10+
Swift 5

A Swift alternative to converting UIView.AnimationCurve to UIView.AnimationOptions, which may not even be possible, is to use UIViewPropertyAnimator (iOS 10+), which accepts UIView.AnimationCurve and is a more modern animator than UIView.animate.

Most likely you'll be working with UIResponder.keyboardAnimationCurveUserInfoKey, which returns an NSNumber. The documentation for this key is (Apple's own notation, not mine):

public class let keyboardAnimationCurveUserInfoKey: String // NSNumber of NSUInteger (UIViewAnimationCurve)

Using this approach, we can eliminate any guesswork:

if let kbTiming = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber, // doc says to unwrap as NSNumber
    let timing = UIView.AnimationCurve.RawValue(exactly: kbTiming), // takes an NSNumber
    let curve = UIView.AnimationCurve(rawValue: timing) { // takes a raw value
    let animator = UIViewPropertyAnimator(duration: duration, curve: curve) {
        // add animations
    }
    animator.startAnimation()
}
trndjc
  • 11,654
  • 3
  • 38
  • 51