禁用-[ CALayer setNeedsDisplayInRect: ]中的隐式动画

我在其-draInContext: 方法中得到了一个包含一些复杂绘图代码的图层。我试图最小化我需要做的绘图量,所以我使用-setNeedsDisplayInRect: 来更新已更改的部分。效果非常好。然而,当图形系统更新我的图层时,它正在使用交叉渐变从旧图像过渡到新图像。我希望它能立刻切换。

我尝试过使用 CATransaction 关闭操作并将持续时间设置为零,但这两种方法都不起作用。下面是我使用的代码:

[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];

在 CATransaction 上我是否应该使用不同的方法(我还尝试了-setValue: forKey: with kCATransactionDisableActions,结果相同)。

52052 次浏览

您可以通过在层上设置操作字典来返回 [NSNull null]作为适当键的动画来实现这一点。例如,我使用

NSDictionary *newActions = @{
@"onOrderIn": [NSNull null],
@"onOrderOut": [NSNull null],
@"sublayers": [NSNull null],
@"contents": [NSNull null],
@"bounds": [NSNull null]
};


layer.actions = newActions;

禁用淡入/淡出动画插入或更改子层内的一个我的图层,以及改变大小和层的内容。我相信 contents的关键是一个你正在寻找,以防止交叉褪色的更新绘图。


快速版:

let newActions = [
"onOrderIn": NSNull(),
"onOrderOut": NSNull(),
"sublayers": NSNull(),
"contents": NSNull(),
"bounds": NSNull(),
]

另外:

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];


//foo


[CATransaction commit];

除了 Brad Larson 的回答: 为自定义层(由您创建) 您可以使用委托而不是修改层的 actions字典。这种方法更加动态,可能性能更好。它允许禁用所有隐式动画,而不必列出所有可动画键。

不幸的是,不可能使用 UIView作为自定义层委托,因为每个 UIView已经是自己层的委托。但是您可以像下面这样使用一个简单的 helper 类:

@interface MyLayerDelegate : NSObject
@property (nonatomic, assign) BOOL disableImplicitAnimations;
@end


@implementation MyLayerDelegate


- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
if (self.disableImplicitAnimations)
return (id)[NSNull null]; // disable all implicit animations
else return nil; // allow implicit animations


// you can also test specific key names; for example, to disable bounds animation:
// if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
}


@end

用法(视图内) :

MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];


// assign to a strong property, because CALayer's "delegate" property is weak
self.myLayerDelegate = delegate;


self.myLayer = [CALayer layer];
self.myLayer.delegate = delegate;


// ...


self.myLayerDelegate.disableImplicitAnimations = YES;
self.myLayer.position = (CGPoint){.x = 10, .y = 42}; // will not animate


// ...


self.myLayerDelegate.disableImplicitAnimations = NO;
self.myLayer.position = (CGPoint){.x = 0, .y = 0}; // will animate

有时将 view 的控制器作为 view 的自定义子层的委托是很方便的; 在这种情况下不需要助手类,您可以在控制器内部实现 actionForLayer:forKey:方法。

重要提示: 不要试图修改 UIView底层的委托(例如,启用隐式动画)ーー坏事情会发生:)

注意: 如果你想动画(而不是禁用动画)层重绘,它是无用的把 [CALayer setNeedsDisplayInRect:]调用内的 CATransaction,因为实际重绘可能(可能会)发生有时以后。好的方法是使用自定义属性,如 在这个答案中所述。

事实上,我没有找到任何正确的答案。为我解决这个问题的方法是:

- (id<CAAction>)actionForKey:(NSString *)event {
return nil;
}

然后您可以使用其中的任何逻辑,来禁用一个特定的动画,但是由于我想删除它们,所以我返回了 null。

基于 Sam 的回答和 Simon 的困难... 在创建 CSShapeLayer 之后添加委托引用:

CAShapeLayer *myLayer = [CAShapeLayer layer];
myLayer.delegate = self; // <- set delegate here, it's magic.

在 M 档案的其他地方。

本质上与 Sam 的相同,不能通过自定义的“ disableImpicity Animations”变量排列进行切换。更像是“硬线”方法。

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {


// disable all implicit animations
return (id)[NSNull null];


// allow implicit animations
// return nil;


// you can also test specific key names; for example, to disable bounds animation:
// if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];


}

当您更改一个层的属性时,CA 通常会创建一个隐式事务对象来使更改具有动画效果。如果不想对更改进行动画处理,可以通过创建显式事务并将其 交易禁用操作属性设置为 没错来禁用隐式动画。

目标 C

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
// change properties here without animation
[CATransaction commit];

斯威夫特

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()

将其添加到您正在实现 -draRect ()方法的自定义类中。对代码进行修改,以适应您的需要,对我来说,“不透明”的技巧,停止交叉淡出动画。

-(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
{
NSLog(@"key: %@", key);
if([key isEqualToString:@"opacity"])
{
return (id<CAAction>)[NSNull null];
}


return [super actionForLayer:layer forKey:key];
}

找到了一个更简单的方法来禁用 CATransaction内部为 kCATransactionDisableActions键内部调用 setValue:forKey:的操作:

[CATransaction setDisableActions:YES];

斯威夫特:

CATransaction.setDisableActions(true)

IOS7中,有一种方便的方法可以做到这一点:

[UIView performWithoutAnimation:^{
// apply changes
}];

这里有一个更有效的解决方案,类似于公认的答案,但对于 斯威夫特。在某些情况下,这比每次你修改一个值时创建一个事务要好,这是一个性能问题,正如其他人所提到的,例如,常见的用例是以60fps 的速度拖动层位置。

// Disable implicit position animation.
layer.actions = ["position": NSNull()]

请参阅苹果公司的 如何解决分层操作文档。实现委托将在级联中跳过另一个级别,但在我的情况下,由于 关于需要将委托设置为关联的 UIView 的警告,这个级别太混乱了。

编辑: 更新感谢评论者指出,NSNull符合 CAAction

要在更改 CATextLayer 的字符串属性时禁用恼人的(模糊的)动画,可以这样做:

class CANullAction: CAAction {
private static let CA_ANIMATION_CONTENTS = "contents"


@objc
func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
// Do nothing.
}
}

然后像这样使用它(别忘了正确设置 CATextLayer,例如正确的字体等) :

caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]

你可以在这里看到 CATextLayer 的完整设置:

private let systemFont16 = UIFont.systemFontOfSize(16.0)


caTextLayer = CATextLayer()
caTextLayer.foregroundColor = UIColor.blackColor().CGColor
caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
caTextLayer.fontSize = systemFont16.pointSize
caTextLayer.alignmentMode = kCAAlignmentCenter
caTextLayer.drawsAsynchronously = false
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
caTextLayer.contentsScale = UIScreen.mainScreen().scale
caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)


uiImageTarget.layer.addSublayer(caTextLayer)
caTextLayer.string = "The text you want to display"

现在可以随心所欲地更新 caTextLayer.string =)

受到 这个这个答案的启发。

试试这个。

let layer = CALayer()
layer.delegate = hoo // Same lifecycle UIView instance.

警告

如果您设置 UITableView 实例的委托,有时会发生崩溃

如果你曾经需要一个非常快速的(但是公认的粗糙的)修复方法,它可能值得你去做(Swift) :

let layer = CALayer()


// set other properties
// ...


layer.speed = 999

在 Swift 中禁用隐式图层动画

CATransaction.setDisableActions(true)

更新为快速和禁用只有一个隐式属性动画在 iOS 而不是 MacOS

// Disable the implicit animation for changes to position
override open class func defaultAction(forKey event: String) -> CAAction? {
if event == #keyPath(position) {
return NSNull()
}
return super.defaultAction(forKey: event)
}

另一个示例,在本例中消除了两个隐式动画。

class RepairedGradientLayer: CAGradientLayer {


// Totally ELIMINATE idiotic implicit animations, in this example when
// we hide or move the gradient layer


override open class func defaultAction(forKey event: String) -> CAAction? {
if event == #keyPath(position) {
return NSNull()
}
if event == #keyPath(isHidden) {
return NSNull()
}
return super.defaultAction(forKey: event)
}
}