如何禁用 CALayer 隐式动画?

发布于 2024-11-04 06:33:31 字数 479 浏览 0 评论 0原文

这让我发疯!我正在开发一个绘图应用程序。假设我正在开发一个名为sheet的UIView

我向此视图添加一些子图层 ([sheet.layer addSublayer:...]),然后我想绘制到其中。为此,我创建一个 CGImageRef 并将其放入图层的内容中。但它是动画的,我不想要那样。

我尝试了一切:

  • removeAnimationForKey:
  • removeAllAnimations
  • 设置动作字典
  • 使用动作层 delegate
  • [CATransaction setDisableAnimations:YES]

它是似乎是正确的。我不明白为什么这一层仍然是动画的;_;
我做错了什么吗?有什么秘密方法吗?

It's driving me crazy! I am working on a drawing application. Let's say I am working on a UIView called sheet.

I am adding some sublayers to this view ([sheet.layer addSublayer:...]) and then I want to draw into them. To do so I am creating a CGImageRef and putting it into the layer's contents. But it's animated and I don't want that.

I tried everything:

  • removeAnimationForKey:
  • removeAllAnimations
  • set the actions dictionary
  • using the actionlayer delegate
  • [CATransaction setDisableAnimations:YES]

It's seems correct. I don't understand why this layer is still animated ;_;
Am I doing something wrong? Is there a secret way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

緦唸λ蓇 2024-11-11 06:33:31

斯威夫特

CATransaction.begin()
CATransaction.setDisableActions(true)

// change layer properties that you don't want to animate

CATransaction.commit()

Swift

CATransaction.begin()
CATransaction.setDisableActions(true)

// change layer properties that you don't want to animate

CATransaction.commit()
ぃ双果 2024-11-11 06:33:31

您必须通过将代码包装在 CATransaction 中来显式禁用动画

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
layer.content = someImageRef;
[CATransaction commit];

You have to explicitly disable animations by wrapping your code in a CATransaction

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
layer.content = someImageRef;
[CATransaction commit];
情定在深秋 2024-11-11 06:33:31

另一种方法:

  1. 您应该禁用sheet.layer的默认动画,它在添加子层时隐式调用。

  2. 您还应该为每个子层添加内容动画。当然,您可以在每次设置sublayer.content时使用CATransaction的“kCATransactionDisableActions”。但是,当您创建子层时,您可以禁用此动画一次。


这是代码:

// disable animation of container
sheet.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                  forKey:@"sublayers"];

// disable animation of each sublayer
sublayer.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                     forKey:@"content"];

// maybe, you'll also have to disable "onOrderIn"-action of each sublayer.       

Another way:

  1. You should disable default animation of your sheet.layer, which is called implicitly when adding sublayer.

  2. You should also content-animation of each sublayer. Of course, you can use "kCATransactionDisableActions" of CATransaction each time you set sublayer.content. But, you can disable this animation once, when you are creating your sublayer.


Here is code:

// disable animation of container
sheet.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                  forKey:@"sublayers"];

// disable animation of each sublayer
sublayer.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                     forKey:@"content"];

// maybe, you'll also have to disable "onOrderIn"-action of each sublayer.       
筱武穆 2024-11-11 06:33:31

从 Mac OS X 10.6 和 iOS 3 开始,CATransaction 还具有一个 setDisableActions 方法,用于设置键 kCATransactionDisableActions 的值。

[CATransaction begin];
[CATransaction setDisableActions:YES];

layer.content = someImageRef;

[CATransaction commit];

在 Swift 中,我喜欢使用这个扩展方法:

extension CATransaction {
    class func withDisabledActions<T>(_ body: () throws -> T) rethrows -> T {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        defer {
            CATransaction.commit()
        }
        return try body()
    }
}

然后你可以像这样使用它:

CATransaction.withDisabledActions {
    // your stuff here
}

As of Mac OS X 10.6 and iOS 3, CATransaction also has a setDisableActions method that sets the value for key kCATransactionDisableActions.

[CATransaction begin];
[CATransaction setDisableActions:YES];

layer.content = someImageRef;

[CATransaction commit];

In Swift, I like to use this extension method:

extension CATransaction {
    class func withDisabledActions<T>(_ body: () throws -> T) rethrows -> T {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        defer {
            CATransaction.commit()
        }
        return try body()
    }
}

You can then use it like this:

CATransaction.withDisabledActions {
    // your stuff here
}
从此见与不见 2024-11-11 06:33:31

Swift 4 扩展:

extension CATransaction {

    static func disableAnimations(_ completion: () -> Void) {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        completion()
        CATransaction.commit()
    }

}

用法:

    CATransaction.disableAnimations {
        // things you don't want to animate
    }

Swift 4 extension :

extension CATransaction {

    static func disableAnimations(_ completion: () -> Void) {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        completion()
        CATransaction.commit()
    }

}

Usage :

    CATransaction.disableAnimations {
        // things you don't want to animate
    }
时光清浅 2024-11-11 06:33:31

层扩展:

extension CALayer {    
    var areAnimationsEnabled: Bool {
        get { delegate == nil }
        set { delegate = newValue ? nil : CALayerAnimationsDisablingDelegate.shared }
    }
}

private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate {
    static let shared = CALayerAnimationsDisablingDelegate()
    private let null = NSNull()

    func action(for layer: CALayer, forKey event: String) -> CAAction? {
        null
    }
}

用法:

anyLayer.areAnimationsEnabled = false

Layer extension:

extension CALayer {    
    var areAnimationsEnabled: Bool {
        get { delegate == nil }
        set { delegate = newValue ? nil : CALayerAnimationsDisablingDelegate.shared }
    }
}

private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate {
    static let shared = CALayerAnimationsDisablingDelegate()
    private let null = NSNull()

    func action(for layer: CALayer, forKey event: String) -> CAAction? {
        null
    }
}

Usage:

anyLayer.areAnimationsEnabled = false
眉黛浅 2024-11-11 06:33:31

这是一个老问题,但问题仍然存在。有时您不想要 CALayer 强加给您的动画。我对基于事务的方法不满意,因为我只想关闭这些操作。为了好的。这是一个子类 CALayer 的 Swift 4 解决方案,允许选择是允许任何操作还是全局禁用它们。您还可以创建具有相同内容的 CAShapeLayer、CATextLayer 子类:

public class ActionCALayer: CALayer {
    public var allowActions: Bool = false

    override public func action(forKey event: String) -> CAAction? {
        return allowActions ? super.action(forKey: event) : nil
    }
}

This is an old question but the problem remains. Sometimes you don't want the animations that CALayer forces on you. I wasn't happy with the transaction based approach as I just wanted to turn these actions off. For good. Here's a Swift 4 solution to subclass CALayer to allow a choice whether to allow any action or globally disable them. You can also create CAShapeLayer, CATextLayer subclasses with the same contents:

public class ActionCALayer: CALayer {
    public var allowActions: Bool = false

    override public func action(forKey event: String) -> CAAction? {
        return allowActions ? super.action(forKey: event) : nil
    }
}
万人眼中万个我 2024-11-11 06:33:31

Swift 2

我能够禁用所有动画,如下所示,其中 myView 是您正在使用的视图:

myView.layer.sublayers?.forEach { $0.removeAllAnimations() }

作为旁注,删除所有图层:

myView.layer.sublayers?.forEach { $0.removeFromSuperlayer() }

Swift 2

I was able to disable all animations as follows, where myView is the view you are working with:

myView.layer.sublayers?.forEach { $0.removeAllAnimations() }

And as a side note, removing all layers:

myView.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
夕色琉璃 2024-11-11 06:33:31

可重用的全局代码:

/**
 * Disable Implicit animation
 * EXAMPLE: disableAnim{view.layer?.position = 20}//Default animation is now disabled
 */
func disableAnim(_ closure:()->Void){
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    closure()
    CATransaction.commit()
}

将此代码添加到代码中的任何位置(全局范围)

Reusable global code:

/**
 * Disable Implicit animation
 * EXAMPLE: disableAnim{view.layer?.position = 20}//Default animation is now disabled
 */
func disableAnim(_ closure:()->Void){
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    closure()
    CATransaction.commit()
}

Add this code anywhere in your code (Globally scoped)

执手闯天涯 2024-11-11 06:33:31

以下解决方案避免在层之前和之后临时使用 +(CATransaction) 并永久设置所需的行为(CALayers 的特定属性没有动画),除非您有意创建操作。这样,您最终会得到更清晰的源代码,清楚地表达该方法是什么,并且仍然拥有 CATransaction 的全部潜在能力。

使用 ie [self.layer addSublayer:yourCALayer] 将图层添加到视图之前以及在添加图层之后,您可以通过覆盖动画来禁用 CALayer 的特定动画属性钥匙。您设置为 NULL 的键以该属性命名,此处显示的内容类似于对 layer.position = CGPoint(x,y); 所做的操作,

yourCALayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];

因为 actions 属性是一个NSDictionary 不允许存储 nil,您可以使用 [NSNull null] 将其显式设置为 NULL 对象,这与 (id)kCFNull 相同代码>
您可以通过迭代视图层的所有子层来对所有子层执行此操作...

for (CALayer *iterationLayer in self.layer.sublayers ) {
    iterationLayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];
    //or for multiple keys at once
    NSNull *nop = [NSNull null];
    iterationLayer.actions = [NSDictionary dictionaryWithObjects:@[nop,nop] forKeys:@[@"position",@"contents"]];
}

The following solution avoids temporarily use of +(CATransaction) before and after layers and sets the needed behaviour (no animation for specific properties of CALayers) permanently unless you create actions on purpose. This way you end up with cleaner source clearly expressing what the approach is and still have the full potential power of CATransaction.

before adding the layer to your view with i.e. [self.layer addSublayer:yourCALayer] and also after its already added you can disable specific animated propertys of your CALayer by overwriting the animation key. The key you set to NULL is named after the property, here shown like its done for the layer.position = CGPoint(x,y);

yourCALayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];

Because the actions property is an NSDictionary which does not allow storing of nil you set it explicit to an NULL object with [NSNull null], which is the same as (id)kCFNull
You can do this for all sublayers by iterating thru all sublayers of the views layer with...

for (CALayer *iterationLayer in self.layer.sublayers ) {
    iterationLayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];
    //or for multiple keys at once
    NSNull *nop = [NSNull null];
    iterationLayer.actions = [NSDictionary dictionaryWithObjects:@[nop,nop] forKeys:@[@"position",@"contents"]];
}
跨年 2024-11-11 06:33:31

我完全同意瑞安的观点。他的答案是针对 MacOS,对于 iOS,您添加以下内容来创建 NSNull() 操作。这个问题 Disabling隐式动画 -[CALayer setNeedsDisplayInRect:] 和文档标题引导我到这里

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

I completely agree with Ryan. His answer is for MacOS, for iOS you add the following to create an NSNull() action. This question Disabling implicit animations in -[CALayer setNeedsDisplayInRect:] and the documentation in the header lead me here

// 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)
}
梦毁影碎の 2024-11-11 06:33:31

在 2011 年,使用 CATransaction 是有意义的,但从 iOS 7 (2013) 开始,您应该使用 +[UIView PerformWithoutAnimation:]。这可以确保您不会留下未提交的 CATransaction,以防 return 语句滑入 begincommit 之间的代码中。

对于某些系统视图,某些更改仅发生在布局上(例如设置 UIButton 的标题时)。在这些视图上调用 layoutIfNeeded

目标 C:

[UIView performWithoutAnimation:^{
    // Your code goes here
    // Optionally layout your view
    [view layoutIfNeeded];
}];

Swift:

UIView.performWithoutAnimation {
    // Your code goes here
    // Optionally layout your view
    view.layoutIfNeeded()
}

In 2011 it made sense to use CATransaction, but since iOS 7 (2013) you should use +[UIView performWithoutAnimation:]. That makes sure you are not left with an uncommitted CATransaction in case a return statement slips in the code between begin and commit.

For some system views, some changes only happen on layout (like when setting the title of a UIButton). Call layoutIfNeeded on those views.

Objective C:

[UIView performWithoutAnimation:^{
    // Your code goes here
    // Optionally layout your view
    [view layoutIfNeeded];
}];

Swift:

UIView.performWithoutAnimation {
    // Your code goes here
    // Optionally layout your view
    view.layoutIfNeeded()
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文