自定义结构体类型的NSView属性使用NSAnimatablePropertyContainer动画异常求助
解决自定义结构体属性的NSAnimatablePropertyContainer动画问题
我之前也碰到过一模一样的情况——Cocoa动画系统对NSSize这类系统内置结构体有原生支持,但面对自定义结构体就会“手足无措”,导致每帧动画都触发-setNilValueForKey:调用。问题的核心很明确:动画生成的中间值会被包装成NSValue对象,但你的setRange:方法只接受BaseRange结构体,运行时找不到匹配的setter,就会 fallback 到调用-setNilValueForKey:。
下面是具体的解决方案,分三步就能搞定:
1. 为自定义结构体添加NSValue包装支持
首先得让BaseRange能被NSValue正确包装和解包,这样动画系统传递的中间值才能被正确解析。创建一个NSValue的分类:
// NSValue+BaseRange.h @interface NSValue (BaseRange) + (NSValue *)valueWithBaseRange:(BaseRange)range; - (BaseRange)baseRangeValue; @end // NSValue+BaseRange.m @implementation NSValue (BaseRange) + (NSValue *)valueWithBaseRange:(BaseRange)range { return [self valueWithBytes:&range objCType:@encode(BaseRange)]; } - (BaseRange)baseRangeValue { BaseRange range; [self getValue:&range]; return range; } @end
2. 重写setValue:forKey:处理NSValue类型的range值
在你的NSView子类中,重写这个方法,当收到range键的NSValue对象时,手动解包成BaseRange结构体,再调用你的setter:
- (void)setValue:(id)value forKey:(NSString *)key { if ([key isEqualToString:@"range"]) { if ([value isKindOfClass:[NSValue class]]) { BaseRange range; [value getValue:&range]; self.range = range; [self setNeedsDisplay:YES]; // 触发视图重绘,更新动画帧 return; } } // 其他键交给父类处理 [super setValue:value forKey:key]; }
3. 为动画添加自定义插值逻辑
最后,在你已经重写的+defaultAnimationForKey:方法中,添加自定义的插值函数,告诉动画系统如何计算BaseRange的中间值:
+ (id)defaultAnimationForKey:(NSString *)key { if ([key isEqualToString:@"range"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"range"]; // 创建自定义插值函数,对start和len分别做线性插值 CAValueFunction *valueFunc = [CAValueFunction valueFunctionWithName:CAValueFunctionNameCustom]; valueFunc.customBlock = ^(const void *fromVal, const void *toVal, void *resultVal, float fraction) { const BaseRange *from = (const BaseRange *)fromVal; const BaseRange *to = (const BaseRange *)toVal; BaseRange *result = (BaseRange *)resultVal; // 计算每一步的start和len值 result->start = from->start + (to->start - from->start) * fraction; result->len = from->len + (to->len - from->len) * fraction; }; anim.valueFunction = valueFunc; return anim; } return [super defaultAnimationForKey:key]; }
完成这三步后,再调用[myView.animator setRange:yourBaseRangeValue],动画系统就能正确生成每一步的BaseRange中间值,不会再触发-setNilValueForKey:,同时视图也会随着动画平滑更新。
内容的提问来源于stack exchange,提问作者jeanlain
相关产品推荐
相关产品推荐

