【问题标题】:Animating NSView property of custom structure type自定义结构类型的动画 NSView 属性
【发布时间】:2023-01-12 21:28:24
【问题描述】:
我有一个 NSView 子类,其标头中的属性声明如下:
@property (nonatomic) BaseRange range;
BaseRange 定义为:
typedef struct BaseRange {
float start;
float len;
} BaseRange;
我想使用 NSAnimatablePropertyContainer 协议为 range 属性设置动画。
我的班级根据需要覆盖+ defaultAnimationForKey:。
问题是,当我调用[myView.animator setRange:<some value>](myView是相关类的一个实例)时,myView在动画的每一步都被发送-setNilValueForKey:用于range键,除了最终值. IOW,动画不起作用。
如果我这样定义 range 属性:
@property (nonatomic) NSSize range;
并且不要更改任何其他内容,不会发送 -setNilValueForKey: 消息,而是像往常一样发送 range 键的中间值。
但我不想使用NSSize,因为range键应该代表一个范围而不是一个大小。
有什么建议么?
【问题讨论】:
标签:
objective-c
nsview
nsanimation
【解决方案1】:
如果您从 +[NSAnimatablePropertyContainer defaultAnimationForKey:] 方法返回 CAPropertyAnimation 或其子类,则行为符合预期,如 it works with strict set of values:
- 整数和双精度数
-
CGRect、CGPoint、CGSize和CGAffineTransform结构
-
CATransform3D数据结构
-
CGColor 和CGImage 参考资料
据我所知,CAAnimation 及其子类无法使用超出此集合的任意值,并且主要专注于使用 Core Graphics 属性(图层、框架、颜色等)。但是,在 macOS 上,您可以改用 NSAnimation 类,这更加灵活,但需要对您的类进行额外的自定义。首先,您应该继承 NSAnimation 本身并覆盖 -[NSAnimation setCurrentProgress:] 方法(这不是强制性的,但否则您将无法在动画步骤之间获得足够平滑的过渡):
- (void)setCurrentProgress:(NSAnimationProgress)currentProgress {
[super setCurrentProgress:currentProgress];
// Range owner refers to the object (view) with the property of custom struct type
// Range Key Path helps to locate the property inside the object
if (!_rangeOwner || !_rangeKeyPath) {
return;
}
static const char *const kTDWRangeEncoding = @encode(TDWRange);
// Wraps new range with NSValue
NSValue *newRange = [NSValue value:&(TDWRange){
.location = (_targetRange.location - _originalRange.location) * currentProgress + _originalRange.location,
.length = (_targetRange.length - _originalRange.length) * currentProgress + _originalRange.length
} withObjCType:kTDWRangeEncoding];
// Sends new value to the object that owns the range property
[_rangeOwner setValue:newRange
forKeyPath:_rangeKeyPath];
}
在这个实现中:
-
TDWRange 指的是表示范围的自定义结构;
-
_rangeOwner 指的是具有TDWRange 类型属性的对象;
-
_rangeKeyPath指的是NSAnimation子类可以找到该属性的关键路径;
-
_targetRange 是动画插入的值;
-
_originalRange 是动画开始前的属性值。
然后,在您的自定义视图类中,您应该提供一种单独的方法来使用给定的动画更新属性。假设动画类名为 TDWRangeAnimation 并且可以通过 @"range" 键路径访问范围属性,这样的方法可能如下所示:
- (void)setRange:(TDWRange)range animated:(BOOL)animated {
if (animated) {
TDWRangeAnimation *rangeAnimation = [[TDWRangeAnimation alloc] initWithRangeOwnder:self
rangeKeyPath:@"range"
targetRange:range
duration:0.4
animationCurve:NSAnimationEaseOut];
rangeAnimation.animationBlockingMode = NSAnimationNonblocking;
[rangeAnimation startAnimation];
} else {
self.range = range;
}
}
您不需要保留动画对象,因为它由运行循环维护直到动画结束。
附言如果您需要完整的实施示例,请随时参考the gist。