iOS:如何将UIViewAnimationCurve转换为UIViewAnimationOptions?

52

UIKeyboardAnimationCurveUserInfoKey的值是UIViewAnimationCurve类型的。我该如何将其转换为相应的UIViewAnimationOptions值,以便在+[UIView animateWithDuration:delay:options:animations:completion:]方法中使用options参数?

// 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;

显然,我可以使用 switch 语句创建一个简单的分类方法,例如:

// 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

但是,有更简单/更好的方法吗?

5个回答

45
你所建议的category方法是“正确”的方法——你不能保证那些常量保持它们的值。不过从它们的定义方式看,似乎你可以只做如下操作:
animationOption = animationCurve << 16;

如果编译器对此有意见,可能需要将其转换为NSUInteger,然后再转换为UIViewAnimationOptions。


9
我建议使用类似于NSAssert(UIViewAnimationCurveLinear << 16 == UIViewAnimationOptionCurveLinear, @"UIViewAnimationCurve的实现出乎意料")这样的语句来保护它。 - lhunath
2
@lhunath 7 用于键盘动画,是私有的。但是可以通过 userInfo 从键盘传递。 - pronebird

38

可以说你可以将您的第一个解决方案变为内联函数,以节省堆栈推动。它是如此紧密的条件(常量限制等),以至于它应该编译成非常小的汇编代码。

编辑: 根据@matt的说法,这里是(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
        }
    }
}

你的 switch 语句中不缺少 break 吗? - Florian
1
"return" 立即发生,所以不。 - David Pisoni
15
这不是一个好的做法。 UIKeyboardWillShowNotification[userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]返回7。在此switch语句中没有这种情况。 查看得票最多的答案。 - derpoliuk
我会担心这样的隐式匹配。我不知道苹果是否会创建一些与移位不匹配的常量。尽管如此,这种方法显然存在不支持新类型的漏洞。 - David Pisoni
我在iOS 7.1上看到动画曲线为7。请参见https://dev59.com/uWMk5IYBdhLWcg3w8SPa获取更好的方法。这段代码会出现奇怪的问题,因为switch语句中没有默认值。 - Seth Spitzer
显示剩余3条评论

19

在 Swift 中,你可以这样做

extension UIViewAnimationCurve {
    func toOptions() -> UIViewAnimationOptions {
        return UIViewAnimationOptions(rawValue: UInt(rawValue << 16))
    }
}

11
使用开关解决方案的一个问题是它假定不会传递任何选项组合。但实践表明,可能存在假设不成立的情况。我发现的一个例子是(至少在iOS 7上),当您获得键盘动画以沿着键盘的出现/消失来动画您的内容时。如果您监听keyboardWillShow:keyboardWillHide:通知,然后获取键盘宣布将使用的曲线,例如:
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];

如果你得到的值是7,如果将其传递给switch函数/方法,则无法正确翻译该值,导致动画行为不正确。

Noah Witherspoon的答案将返回正确的值。结合这两个解决方案,你可以编写类似以下的代码:

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

正如Noah所指出的那样,这里需要注意的是如果苹果公司改变了枚举值而使得这两种类型不再对应,那么这个函数将会失效。使用它的原因是基于switch的选项并不能在你今天可能遇到的所有情况下都起作用,而这个函数可以。


2
如果您在UIKeyboardAnimationCurveUserInfoKey中获取到了7的值,那听起来像是一个bug。文档说明该值为UIViewAnimationCurve。UIViewAnimationCurve被定义为NS_ENUM,而不是NS_OPTIONS。因此,唯一可能的值是0、1、2和3。任何其他值都是没有意义的。另一方面,UIViewAnimationOption被定义为NS_OPTIONS,可以有大约32,000个不同的值。即使是Noah的解决方案也无法处理值为7的情况,它只会将其视为传递了UIViewAnimationCurveLinear。 - Senseful

5

iOS 10+
Swift 5

在 Swift 中,将 UIView.AnimationCurve 转换为 UIView.AnimationOptions 可能并不可行。作为替代方案,可以使用 UIViewPropertyAnimator(iOS 10+),该类接受 UIView.AnimationCurve 并比 UIView.animate 更为现代化。

你可能会经常使用 UIResponder.keyboardAnimationCurveUserInfoKey,它返回一个 NSNumber。关于此键的文档是(这是苹果的注释,而非我的):

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

使用这种方法,我们可以消除任何猜测:

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()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接