141

我在它的 -drawInContext: 方法中有一个包含一些复杂绘图代码的层。我试图最小化我需要做的绘图量,所以我使用 -setNeedsDisplayInRect: 来更新更改的部分。这工作非常出色。但是,当图形系统更新我的图层时,它会使用交叉淡入淡出从旧图像过渡到新图像。我希望它立即切换。

我尝试使用 CATransaction 关闭操作并将持续时间设置为零,但都不起作用。这是我正在使用的代码:

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

我应该使用 CATransaction 上的不同方法吗(我也尝试了 -setValue:forKey: 与 kCATransactionDisableActions,结果相同)。

4

15 回答 15

178

[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(),
    ]
于 2010-02-11T13:37:14.977 回答
90

还:

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

//foo

[CATransaction commit];
于 2011-03-30T16:11:20.853 回答
32

当您更改层的属性时,CA 通常会创建一个隐式事务对象来为更改设置动画。如果您不想为更改设置动画,您可以通过创建显式事务并将其kCATransactionDisableActions属性设置为true来禁用隐式动画。

Objective-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()
于 2014-07-08T10:05:32.913 回答
23

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

不幸的是,不可能将UIViews 用作自定义层委托,因为每个UIView都已经是其自己层的委托。但是你可以使用这样一个简单的辅助类:

@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

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

重要提示:不要尝试修改UIView's 底层的委托(例如启用隐式动画)- 会发生不好的事情 :)

注意:如果你想动画(不禁用动画)图层重绘,将[CALayer setNeedsDisplayInRect:]call 放在 a 内是没有用的CATransaction,因为实际重绘可能(并且可能会)有时会发生。好的方法是使用自定义属性,如本答案中所述。

于 2013-04-19T02:32:14.293 回答
9

这是一个更有效的解决方案,类似于接受的答案,但适用于Swift。在某些情况下,它会比每次修改值时创建事务要好,这是其他人提到的性能问题,例如以 60fps 左右拖动层位置的常见用例。

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

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

编辑:感谢评论者指出NSNull符合CAAction.

于 2015-10-29T23:02:08.603 回答
8

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

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

然后你可以使用其中的任何逻辑来禁用特定的动画,但是由于我想将它们全部删除,所以我返回了 nil。

于 2013-05-21T10:48:40.237 回答
7

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

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

...“m”文件中的其他地方...

本质上与 Sam 相同,但无法通过自定义的“disableImplicitAnimations”变量排列进行切换。更多的是“硬线”方法。

- (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];

}
于 2013-12-03T21:07:33.333 回答
6

找到了一种更简单的方法来禁用CATransaction内部调用密钥setValue:forKey:的操作:kCATransactionDisableActions

[CATransaction setDisableActions:YES];

迅速:

CATransaction.setDisableActions(true)
于 2015-03-24T11:10:27.183 回答
6

在 Swift 中禁用隐式层动画

CATransaction.setDisableActions(true)
于 2018-04-10T17:08:39.770 回答
4

更新为在 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)
    }
}
于 2019-07-11T00:54:39.700 回答
2

将此添加到您正在实现 -drawRect() 方法的自定义类中。对代码进行更改以满足您的需求,对我来说,“不透明度”可以阻止交叉淡入淡出动画。

-(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];
}
于 2015-01-14T11:53:01.963 回答
1

如果您需要一个非常快速(但公认的 hacky)修复它可能值得做(Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999
于 2016-05-28T09:12:32.870 回答
0

iOS 7开始,有一种方便的方法可以做到这一点:

[UIView performWithoutAnimation:^{
    // apply changes
}];
于 2015-08-01T18:39:51.130 回答
0

要在更改 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 =)

受此启发,以及答案

于 2016-03-11T20:28:58.453 回答
0

尝试这个。

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

警告

如果设置 UITableView 实例的委托,有时会发生崩溃。(可能是滚动视图的 hittest 被递归调用。)

于 2016-04-06T17:04:32.613 回答